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 83× 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

Update — September 23, 2026: the shipped solver has since been reworked — probability-threshold pruning and a transposition table replaced the depth-sampling engine measured here, following the approach of Robert Xiao’s canonical 2048 AI (nneonneo). Every figure below has been re-measured on the reworked engine (re-swept 23 September 2026): adaptive reach-2048 rose from 70% to 94%, and the machine-readable data is published as an open dataset.

0%
reach 2048 at depth ≤ 2
66%
reach 2048 the moment depth hits 3
83×
more compute per move, depth 3 → 6
94%
adaptive reach — a 250-game run confirms 92.8%

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 · 94% 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 5,100, the low plateau of a player that stacks tiles without a plan. Then at depth 3 it clears 2048 in 66% of games and the mean score jumps to 29,648, 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.044 ms at depth 3, 0.92 ms at depth 5, and 3.67 ms at depth 6 — about 83× more compute across those three plies, for a reach-rate that climbed from 66% to 97% and then stopped climbing.

0.002 1 0.008 2 0.044 3 0.203 4 0.919 5 3.667 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.002
2 300 0% 0% 5,117 4,544 0.008
3 300 66.3% 13.3% 29,648 28,680 0.044
4 300 66% 15.3% 29,990 31,416 0.203
5 100 97% 66% 61,019 67,432 0.919
6 30 86.7% 60% 51,914 60,280 3.667
adaptive 300 94% 70.3% 62,529 60,644 1.844

Measured 23 September 2026 on one laptop (reworked engine: pruning + transposition table). 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 94% reach is corroborated by a separate 250-game run at 92.8%.

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 reaches 2048 the most often here — 97% — but it pays 0.92 ms on every move, including the early moves on a wide-open board where deep thinking changes nothing. A constant depth-3 search is nearly free but tops out at 66%.

The shipped engine spends its compute where it matters: shallow (depth 4) while the board is open and moves are obvious, deeper as it fills, and up to depth 7 once a wrong move would end the game. That yields the strongest overall play in the sweep — the highest mean score of any setting (62,529) and the most games reaching 4096 (70%) — while spending almost nothing on the easy early moves. It searches deeper than any fixed setting in the sweep exactly when depth matters, and cheaply when it doesn’t: varying the depth 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 →

How to cite this page

APALK Forge. (2026). How Much Search Does 2048 Need? An Expectimax Depth Benchmark. Retrieved October 26, 2026, from https://lkforge.com/blog/2048-expectimax-depth-benchmark/

BibTeX
@misc{lkforge_2048_depth_benchmark,
  author       = {{LK Forge}},
  title        = {How Much Search Does 2048 Need? An Expectimax Depth Benchmark},
  year         = {2026},
  howpublished = {\url{https://lkforge.com/blog/2048-expectimax-depth-benchmark/}},
  note         = {Accessed 2026-10-26}
}
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 66%. Quality keeps climbing to 97% 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 94% of 300 games at 1.8 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 (66.3% to 66%) while compute roughly quadrupled. The next real gain came at depth 5. And depth 6 cost about 4× more per move than depth 5 for no 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 94% of games; a separate 250-game run of the same engine measured 92.8% — a sign the harness is stable rather than cherry-picked.