Game AI Benchmark

How Much Search Does 2048 Need? An Expectimax Depth Benchmark

By Lucian — builder & engineer, LK Forge

A 2048 AI that searches only one or two moves ahead never reaches the 2048 tile — not rarely, never. We took the exact expectimax engine the game ships, capped its search at each fixed depth from 1 to 6, and self-played hundreds of games at each. The result is a sharp threshold, a plateau, and a compute bill that grows 127× for the last few plies — which is why the shipped engine varies its depth instead of always digging deeper.

 ·  7 min read  ·  every number from a re-runnable headless benchmark

0%
reach 2048 at depth ≤ 2
57%
reach 2048 the moment depth hits 3
127×
more compute per move, depth 3 → 6
70.3%
adaptive reach — matches our 250-game run

How we measured it

The engine under test is the one the 2048 game actually ships — an expectimax search with a corner-snake heuristic, pulled out of the production file and driven from a script with no browser and no human. Normally its depth adapts to the board: three plies when there is lots of space, up to five when the board is nearly full. For this benchmark we added one optional switch that pins the search to a single fixed depth, then self-played the game to a game-over at each depth from 1 to 6.

Depths 1–4 and the adaptive default are 300 games each. Depths 5 and 6 are 100 and 30 games, because each move there is far slower and a full run would take hours — so their percentages carry wider error bars, and we report the exact sample size on every row rather than hide it. Tile spawns use a seeded generator so the run is re-runnable; the search itself samples empty cells with the same randomness the shipped engine uses, so reach-rate is a stable distribution, not a single fixed value. Timing is mean milliseconds per move on one laptop.

0% 25% 50% 75% 100% shipped adaptive · 70.3% reach 2048 1 2 3 4 5 6 fixed search depth (plies) →

Share of games reaching each tile by fixed search depth. Reach 2048 and reach 4096. The dashed amber line is the shipped adaptive engine. Depth 1–2 sit flat on zero for 2048.

The cliff at depth 3

The most striking thing in the data is not a curve, it is a wall. At depth 1 and depth 2 the solver reached the 2048 tile in zero of 300 games each — mean scores stuck around 4,400 and 4,950, the low plateau of a player that stacks tiles without a plan. Then at depth 3 it clears 2048 in 57% of games and the mean score jumps to 26,274, a six-fold leap for one extra ply.

This is the opposite of what tic-tac-toe showed, where even a one-ply search wins most games. 2048 is stochastic and its rewards are deferred: a merge to a higher tile is several moves away, so a shallow search optimises the immediate board and never sets up the chain that a high tile requires. Three plies is the point where the search can finally see a merge forming and start playing toward it.

What each extra ply costs

The gains are not free. Expectimax branches on every empty cell and every possible spawn, so each extra ply multiplies the work. A move takes 0.088 ms at depth 3, 3.07 ms at depth 5, and 11.18 ms at depth 6 — about 127× more compute across those three plies, for a reach-rate that climbed from 57% to 87% and then stopped climbing.

0.003 1 0.01 2 0.088 3 0.306 4 3.069 5 11.184 6 fixed search depth (plies) → ms/move

Mean milliseconds per move, linear scale. The bars are invisible until depth 4 for the same reason the shipped engine can afford a shallow search early: on an open board, thinking hard is cheap and pointless.

Every number

DepthGames20484096Mean scoreMedianms/move
1 300 0% 0% 4,366 3,636 0.003
2 300 0% 0% 4,950 4,112 0.01
3 300 57.3% 11% 26,274 26,472 0.088
4 300 57.7% 14.3% 26,841 26,492 0.306
5 100 87% 52% 50,600 46,624 3.069
6 30 86.7% 66.7% 53,464 60,240 11.184
adaptive 300 70.3% 29% 34,539 31,868 0.57

Measured 9 September 2026 on one laptop. Depth 5 (100 games) and depth 6 (30 games) use smaller samples than the 300-game rows, so their percentages are approximate. The adaptive row is the engine the game ships with; its 70.3% reach reproduces the 69.6% from our earlier 250-game run.

Why the shipped engine varies its depth

Put the reach-rate and the cost side by side and the design falls out on its own. A constant depth-5 search is the strongest here — 87% reach 2048 — but it pays 3.07 ms on every move, including the early moves on a wide-open board where deep thinking changes nothing. A constant depth-3 search is cheap but tops out at 57%.

The shipped engine spends its compute where it matters: shallow (depth 3) while the board is empty and moves are obvious, deep (depth 5) once the board fills up and a wrong move ends the game. That buys 70.3% reach at 0.57 ms/move — well above the depth-3 baseline for a fifth of constant-depth-5’s cost. It does give up the top reach-rate a constant deep search would reach; the point of the benchmark is that the tradeoff is a deliberate, measured choice rather than a guess.

Reproduce it yourself

The engine is public: solver-core.js is the exact file the game loads, and it runs unmodified in Node. Save it, then self-play at any fixed depth — selfPlayGame(rng, depth) is the whole harness.

import { readFileSync } from 'node:fs';
import vm from 'node:vm';

// solver-core.js downloaded from lkforge.com/games/2048/
vm.runInThisContext(readFileSync('solver-core.js', 'utf8'));

let reached = 0;
for (let i = 0; i < 300; i++) {
  const g = globalThis.LK2048.selfPlayGame(Math.random, 3); // fixed depth 3
  if (g.maxTile >= 2048) reached++;
}
console.log((100 * reached / 300).toFixed(1) + '% reached 2048');

Drop the third argument and you get the shipped adaptive engine instead. The solver’s heuristic and search live in the open-source 2048-ai-solver repository.

Watch it play

The 2048 solver runs live in your browser — ask it for a move, or let it autoplay and watch the depth-5 endgame it was built for.

Open the 2048 AI →
Share this X Facebook Reddit

Related reading

Common questions

How much search depth does a 2048 AI actually need?

In our benchmark of the shipped expectimax solver, a search depth of 1 or 2 plies essentially never wins — 0% of games reached the 2048 tile, because two plies cannot see a merge chain form. Depth 3 is the threshold: reach-rate jumps to 57%. Quality keeps climbing to 87% at depth 5, but each extra ply costs 3–10× more compute. The game ships with an adaptive depth (3 when the board is empty, up to 5 when it is nearly full), which reached 2048 in 70.3% of 300 games at 0.57 ms per move.

Why does 2048 behave so differently from tic-tac-toe at shallow depth?

Tic-tac-toe is deterministic and tiny, so even a 1-ply search plays well. 2048 is stochastic — every move spawns a random tile — and the reward (a merge to a higher tile) is several moves away, so a shallow search optimises for the wrong thing and stalls in the low thousands. It needs enough lookahead to value a board that is being set up for a future merge, which is why performance is near-zero below depth 3 and then jumps sharply.

Is deeper search always better?

No. From depth 3 to depth 4 reach-rate barely moved (57.3% to 57.7%) while compute tripled. The next real gain came at depth 5. And depth 6 cost about 3.6× more per move than depth 5 for no clear improvement in reach-rate within our sample. Diminishing, and eventually negative, returns on compute are the norm — the reason the shipped engine adapts its depth rather than always searching as deep as possible.

How reliable are these numbers?

Every figure comes from the exact engine the game runs (solver-core.js), self-played headlessly. Depths 1–4 and the adaptive setting are 300 games each; depths 5 and 6 are 100 and 30 games because each move is far slower, so their percentages carry wider error bars. The adaptive run reached 2048 in 70.3% of games, reproducing our earlier 250-game figure of 69.6% — a sign the harness is stable rather than cherry-picked.