Cairn
algorithms · game trees · O(c^k) for k plies of forcing captures with per-ply branching c ≤ the full branching factor b — cheap next to extending a full search that deep, but blind to quiet threats past the horizon

back to Game Trees

Quiescence Search

Quiescence Search fixes a specific failure mode of any depth-limited search: Iterative Deepening's own heuristic evaluator gets called the instant the search budget runs out, wherever the cutoff happens to land — and if it lands in the middle of a forced trade, the number it reports can be badly misleading. A position that's mid-capture looks nothing like a position that's actually settled: material is temporarily up or down by whatever was just taken, and the recapture that would even things out is sitting one ply past the horizon, invisible to a static evaluator that only ever looks at the board in front of it. Quiescence search's fix is narrow and cheap: at a cutoff, don't evaluate immediately — keep searching, but only through capturing moves (the "loud" ones), until the position reaches a point with no capture left on the table at all (a quiet position), and only then hand it to the static evaluator. Every other page on this site's Game Trees list either decides a move outright or caches/keys a position it's already scored; this one decides something narrower still — whether a leaf is trustworthy enough to evaluate yet.

Try it

The five boxes below model one square under a chain of forced captures — the simplified case quiescence search exists for (real engines call the full version of this static exchange evaluation). At each ply the side to move can either stand pat — decline the capture, banking the material total exactly as it stands — or capture, changing the running total by that ply's value and handing the same choice to the other side. Pick a nominal search depth, then compare what a plain depth-limited search reports against what quiescence search reports for the identical budget.

fixed-depth search — horizon cuts off after the selected depth

quiescence search — keeps following captures regardless of the selected depth

Why it works

Both searches share the exact same shape: at every ply, the side to move picks whichever is better for them, standing pat (keep the current total) or capturing (recurse one ply deeper with the total updated). White maximizes that choice, Black minimizes it — the same alternation as Minimax. The only difference is what's allowed to stop the recursion. Fixed-depth search stops on two conditions: the chain runs out of captures (quiet), or the nominal depth budget hits zero — whichever comes first, no matter which one it is. Quiescence search drops the second condition entirely for capturing moves: the budget still governs how deep a quiet continuation gets searched, but a capture is never refused just because the counter hit zero. The chain above only has one kind of continuation (another capture) until it runs out at ply 5, so quiescence search always plays it out to the real end regardless of the selected depth, landing on the same value a full, unlimited search would find.

The stand-pat option is what keeps that value honest. Without it, a side would be forced to capture whenever one is available even when doing so makes their own position worse — and the true, correctly computed value of this chain (+1, checked directly with the widget above at depth 5) shows exactly why that matters: White wins the first pawn (+1), and Black's own best response, computed by the same recursion, is to decline the recapture rather than walk into a losing continuation three plies deeper. A search without a stand-pat option can't represent that choice at all — see Pitfalls below for exactly how far off the answer gets.

Reference implementation

// captures[i].delta is the running-total change if ply i's capture is made;
// White moves on even plies, Black on odd. Both functions return the eval
// from White's perspective, with each side free to decline (stand pat).

function search(idx, evalBefore, depth) {
  if (idx >= captures.length) return evalBefore; // quiet — nothing left to capture
  if (depth === 0) return evalBefore;             // horizon — trust the static number here
  const evalAfter = evalBefore + captures[idx].delta;
  const continued = search(idx + 1, evalAfter, depth - 1);
  const white = idx % 2 === 0;
  return white ? Math.max(evalBefore, continued) : Math.min(evalBefore, continued);
}

function quiesce(idx, evalBefore) {
  if (idx >= captures.length) return evalBefore; // quiet — same stopping condition as search()
  const evalAfter = evalBefore + captures[idx].delta;
  const continued = quiesce(idx + 1, evalAfter); // depth never gates a capturing move
  const white = idx % 2 === 0;
  return white ? Math.max(evalBefore, continued) : Math.min(evalBefore, continued);
}

That's the whole algorithm: quiesce is search with the horizon check deleted. A real engine's quiescence search adds two things this toy chain doesn't need to show its point: alpha-beta pruning on top (the exact same guarantee established on Minimax's own page — an identical answer for less work, never a different one), and, because captures alone are guaranteed to terminate (there's only finitely much material on a board) but checks aren't, most real implementations also cap how many plies quiescence is allowed to extend through checks specifically, as a safety net against a forced-check line that never naturally quiets down.

Pitfalls

A fixed depth's evaluation doesn't degrade gracefully — it flickers between right and wrong depending on exactly where the horizon lands. Checked directly with the widget above across every depth from 1 to 5: the fixed-depth eval comes back +1, 0, +1, 0, +1 — correct at odd depths, wrong at even ones, alternating for no reason a user watching only the final number would guess. Depth 2 happens to cut off right after Black's recapture, which looks like a fair trade (0) from that snapshot alone; a search that stopped there has no way to know Black wouldn't actually make that recapture in real play, once White's own follow-up three plies later is visible. This isn't a case of "deeper is always more accurate, just costlier" — depth 4 is a strictly deeper search than depth 3 and returns a worse answer, purely because of where its particular horizon happens to fall in this exchange. Quiescence search returns +1 at every one of the same five depth settings, because it never lets the horizon fall mid-capture in the first place.

Drop the stand-pat option and the answer isn't just off — it's off by the width of the entire exchange. Checked directly against a variant of the reference implementation above with the Math.max(evalBefore, continued) / Math.min(evalBefore, continued) stand-pat comparison removed (each side forced to capture whenever one is on the table): the chain plays out to its very end no matter who's "winning" along the way, returning +5 — the sum of every delta in the chain — instead of the true +1. That's not a rounding error; it's claiming White is up a rook-and-a-half more than they actually are, because the forced version can't represent Black ever declining a bad trade.

Quiescence search only ever extends through captures — it doesn't rescue every kind of horizon blindness. Both functions above have no branch at all for a quiet developing move, a positional threat, or a slow-building attack that never involves a capture; quiesce's stopping condition is "no capture left," not "nothing left that matters." A quiet tactical idea sitting just past the horizon is exactly as invisible to quiescence search as it is to plain fixed-depth search — the fix is narrow by design, aimed specifically at the forced-exchange instability demonstrated above, not at horizon effects generally.

Complexity

Time: on this page's own five-ply chain, fixed-depth search visits exactly depth + 1 nodes (checked directly: 2, 3, 4, 5, 6 nodes for depths 1 through 5), and quiescence search visits 6 — the full chain plus its final quiet check — regardless of the selected depth, since nothing here ever gates a capture. The chain is deliberately a straight line (one capture opportunity per ply, no branching) to isolate the stand-pat and horizon mechanics without anything else moving at the same time; a real position usually has more than one legal capture available at a given ply. Generalized, quiescence search costs O(c^k), where k is how many plies of forcing captures lie along a line and c is the branching factor restricted to capturing moves only (c ≤ b, the full branching factor Minimax's own Complexity section uses) — cheap next to extending a full O(b^d) search that same k plies deeper, precisely because most legal moves in a real position are quiet, not captures. Space: O(k) for the recursion stack, the same single-line-of-play footprint as every other entry in this category.

This is the site's eighth Game Trees entry, and like Transposition Tables and Zobrist Hashing it doesn't decide a move by itself — it decides something one level removed from that. Where Transposition Tables decides whether a position is worth re-deriving and Zobrist Hashing decides how cheaply to name one, this page decides whether a position reached at the search horizon is settled enough to trust a static evaluator's number at all. It pairs most directly with Iterative Deepening, the entry that introduced this site's own heuristic-at-cutoff idea in the first place — quiescence search is what keeps that heuristic from being asked to judge a position mid-exchange, exactly the gap Minimax's own fixed-depth board never has to worry about, since its four-empty-cell demo always searches all the way to a genuine win, loss, or draw. See Choosing a Game Tree Search Algorithm for how all ten compare side by side.