The math behind Mathdle
Mathdle looks like a casual word game with numbers, but the engine underneath is unusually strict. Every expression you submit is parsed into a tree, evaluated in exact arithmetic, checked against a set of domain and identity rules, and then hashed into a canonical form that decides whether you've found something new. This page walks through exactly what happens, in the same order the engine does it.
The expression tree
When you submit 3 + 4 × 5, the engine doesn't see a string. It sees a small tree: a + node with a leaf 3 on the left and a × node on the right, and that × node has leaves 4 and 5. Leaves are tagged by tile ID, not by value. That distinction matters: if a puzzle hands you two tiles that both show a 5, they are still different tiles, and the engine never lets you double-spend a single tile under the cover of its value matching another.
Every check downstream — the value, the canonical hash, the duplicate-tile guard — operates on this tree. The pretty string you see on screen is rendered from it, not the other way around.
Exact rational arithmetic over BigInt
Every value in Mathdle is a rational number, represented as a pair of arbitrary-precision integers: a numerator and a denominator, both BigInt. There is no floating-point math anywhere in the engine. 3 ÷ 7 × 7 equals exactly 3, not 2.9999999999or 3.0000000001.
Each rational is stored in lowest terms. The constructor divides numerator and denominator by their greatest common divisor and pins the sign onto the numerator, so the same value always has the same internal representation. 2/4, 1/2, and -3/-6 all collapse to one canonical form. Equality is a single integer comparison on each component.
Operations are textbook fraction arithmetic. Addition cross- multiplies and adds numerators; multiplication multiplies straight across; division flips and multiplies; subtraction is addition with a negated right side. Division by zero throws; the engine catches it and reports a domain failure rather than crashing.
Canonicalization: when two expressions are “the same”
The catalogue of solutions for each puzzle is keyed by a canonical string. If two expression trees produce the same key, they count as the same expression and only one of them earns points. The rules:
- Commutativity is the same. Inside a
+or×chain, the order of the terms doesn't matter.3 + 5and5 + 3hash to the same key. - Associativity is the same. The engine flattens every
+/−chain into a multiset of positive and negative terms, and every×/÷chain into a multiset of numerator and denominator factors. So(2 + 3) + 4,2 + (3 + 4), and4 + 2 + 3all collapse to the same flat shape. - Distributivity is different. The tree shape is preserved across
×-over-+boundaries.2 × (3 + 4)stays a product with a sum inside it;2 × 3 + 2 × 4stays a sum of two products. These are different keys, and both count. - Subtraction and division flatten into sign and direction. The flattener tracks a sign for each additive term and a direction (numerator or denominator) for each multiplicative factor. So
a − b + cbecomes{a, c}positive and{b}negative;a × b ÷ cbecomes{a, b}on top and{c}on the bottom. Reordering inside those multisets doesn't change the key. - mod, ^, √, and ! are opaque.They aren't chainable like
+or×, so the canonicalizer treats them as sealed sub-trees: the operator name plus its operands in fixed order. Inside an enclosing additive or multiplicative chain, they act as a single atom. - Leaves use tile IDs. Two tiles with the same face value are distinguishable. Swapping which of the two
5tiles you used produces a different key, because the engine wants to credit you for finding a genuinely different construction — even one that happens to look identical on screen.
The practical consequence: every multiplicative expression you find has a distributed cousin, and every distributed sum has a factored cousin. Both score. The tips page covers how to milk this for points.
Identity rejection
The engine walks the tree and refuses any sub-expression whose only effect is to consume a tile without changing the value. These would otherwise be a trivial way to pad a pangram. The full list:
x + 0andx − 0— adding or subtracting zero anywhere in the tree fails.x × 1andx ÷ 1— multiplying or dividing by one fails.x × 0also fails, as does anything that short-circuits a chain to zero.x^1,x^0, and1^x— power identities all fail. Any one of these collapses the operand.x mod 1— always0, no useful work done.0 mod yis also rejected.√1— same shape in, same shape out. (√0is also rejected.)0!,1!, and2!— the first two return1and the third returns the same value that went in, so all three are no-ops.
The check is recursive. An identity anywhere — top of the tree, deep inside a parenthesized fragment, hidden under a square root — kills the whole submission. The error message tells you which one tripped it.
Impossible-mode operators
Easy, Medium, and Hard use only the base four (+ − × ÷). Impossible adds four more, each with its own domain restrictions.
mod (Euclidean modulo)
a mod b is the remainder of integer division. Both operands must be integers — if either is a fraction, the engine throws. The result is the Euclidean remainder: it has the same sign as the divisor and lies in the half-open interval [0, |b|) for positive divisors. So 17 mod 5 is 2, and (−7) mod 3 is 2, not −1. Mod by zero throws; x mod 1 is an identity and rejected.
√ (integer square root)
√x requires x to be a non-negative integer anda perfect square. The engine computes the integer square root with Newton's method on bigints, then verifies the result squares back exactly. If it doesn't, the expression is rejected. √49 = 7 is fine; √50 is not, and neither is √(1/4). The reason is precision: irrational roots can't be represented as rationals, and approximating them would break the “√x × √x = x” guarantee everything else relies on.
^ (integer exponent)
a ^ b requires b to be an integer with |b| ≤ 4. The cap exists because exponents grow fast: 9^5 is already 59049, and Mathdle targets sit in the low thousands at most. Negative exponents invert (2^−3 = 1/8); 0^0 throws as undefined; 0 to any negative power throws. a^1, a^0, and 1^a are identities and rejected. The base can be any rational, so (1/2)^3 = 1/8 is well-defined.
! (factorial)
n! requires n to be a non-negative integer with n ≤ 8. The cap is hard: 8! = 40320 and 9! = 362880, well past anything a puzzle target asks for. 0!, 1!, and 2! are identities and rejected — they consume a tile without producing anything new. 3! = 6, 4! = 24, 5! = 120, 6! = 720, 7! = 5040, 8! = 40320. Factorials of fractions or negatives throw.
Why exact rationals, not floats
JavaScript's native numbertype is a 64-bit IEEE 754 float. It can't exactly represent 0.1, much less 1/3 or 1/7. The familiar embarrassments — 0.1 + 0.2 === 0.30000000000000004— are harmless when you're rendering a temperature, and a small disaster when you're comparing puzzle solutions for equality.
Mathdle needs three things from its arithmetic, and floats can't deliver any of them on their own:
- Deterministic comparison.When the engine asks “does this expression equal the target?”, the answer has to be a clean yes or no across every device. Floats round in platform-dependent ways. Rationals don't round at all.
- Algebraic identities that hold. Canonicalization relies on the fact that
(a × b) ÷ b = aexactly, that√(n²) = nexactly, and so on. The moment those identities become approximate, the catalogue gets nondeterministic and the engine starts double-counting or under-counting expressions. - Large intermediate values.
7!already exceeds5000, and on Impossible a chain like(7! × 6!) ÷ (5! × 3!)goes well past four million before reducing.BigInthandles those cleanly; a naive integer type would overflow.
The cost of all this is mild: every binary operation does a couple of bigint multiplications and a GCD reduction. Rationals are slower than floats on a hot loop, but Mathdle's hot loop is “evaluate one expression a human just typed,” not anything performance-critical. The catalogue enumeration that runs offline can take a few seconds per puzzle; on the playing client, every check is instantaneous.
Putting it together
When you submit an expression, the engine does six things in order: parse it into a tree, walk the tree to enforce one-use-per-tile and require the center tile, evaluate it exactly with rational arithmetic, compare the value to the target, scan for identity sub-expressions, and finally hash the tree into a canonical key. If the key is already in your found set, you've resubmitted a known shape. If it's new andit's in the puzzle's catalogue, you score.
That's the whole engine. Everything else — the tier curve, the pangram bonus, the streak freeze — is bookkeeping on top.
For practical advice on exploiting these rules at the board, see tips & strategy. For short definitions of the terms used here, see the glossary.