Fast Enough to Feel Instant: Search Algorithms Inside One Browser Tab
No backend, no worker thread — the whole adversarial search runs between two frames on the user's device. Here's what keeps it under the frame budget.
· 7 min read · all numbers from re-runnable headless benchmarks
By the Numbers
The Frame Budget
There is no server in this picture. When the tic-tac-toe opponent picks a move, or the 2048 solver evaluates a tile placement, or Color Lines finds a path, the entire search runs on the main thread, in the same tab as the UI, between two paints. Miss that budget and the game stutters — there is no loading spinner to hide behind, because there was never a request to wait on.
That constraint is what makes the algorithm choice load-bearing. A search that is asymptotically correct but slow is not a shippable feature here — it is a dropped frame. So the interesting engineering question was never "does the AI play well," it was "does it still play well once it has to finish inside a fraction of a frame."
What Alpha-Beta Buys
Tic-tac-toe's search tree is small enough to brute-force, which makes it a clean place to measure what pruning actually does. Alpha-beta pruning skips branches that provably cannot change the outcome, without changing the result — same optimal move, far fewer positions visited. Full write-up of the algorithm is in how the minimax AI works; here's what it measures out to.
Nodes explored, plain minimax vs. alpha-beta (full depth 9)
Each pair is scaled to its own position so both bars stay readable — the opening move and the mid-game position have very different tree sizes. At the opening, alpha-beta cuts 549,945 nodes down to 36,528: a 93% reduction, for the identical optimal move.
Adaptive Depth and Candidate Pruning
Alpha-beta pruning alone doesn't survive a bigger board. Tic-tac-toe's 3×3 grid keeps the full-depth tree small enough to search exhaustively every move; a larger board's branching factor grows fast enough that the same brute-force search would blow straight through the frame budget. The shipped code handles this two ways, both visible in the source rather than benchmarked here.
First, the search depth is capped and adapts to board size rather than staying fixed at "search everything." Second, instead of evaluating every empty cell as a candidate move, the search narrows to cells near existing pieces — the squares that can plausibly matter to the next few moves — rather than scoring the entire empty board on every ply. Neither trick is free: both trade a small amount of theoretical optimality for a bounded amount of work per move, which is the correct trade when the alternative is a dropped frame.
The result is qualitative, not a benchmark number: the tree stops exploding because the search stops trying to look at all of it. We don't have a re-runnable measurement for the larger boards to put a figure on, so none is printed here — the 3×3, full-depth numbers above are the ones we can actually stand behind.
Instant by Design
The same pattern holds across the other two search algorithms on the site. The 2048 solver's expectimax search averages 0.5 ms per move. Color Lines' BFS pathfinding answers a hint query in 0.006 ms. Both numbers come from the same class of headless self-play benchmark as the tic-tac-toe figures above — measured against the shipped code, not a lab version of it.
None of this runs on a server, because none of it is deployed as one — the site is static, vanilla JS with zero dependencies, served at Cloudflare's edge. There's no round trip to shave because there was never a round trip; the only latency budget that matters is the one inside a single browser tab. That's the same discipline behind the rest of this build — see 26 Repos in 29 Days for the wider retrospective on shipping it.
Feel the Frame Budget Yourself
Play a game against the search that's doing all this work in the background — no lag, no spinner, no server round trip.