The horizon problem
The “horizon problem”
Whenever we explore a tree to a fixed depth, we have the “horizon problem.” It may be that a really good move is invisible because it is beyond our horizon (at a greater depth than we have searched). By the same token, there may be a really good move for our opponent just beyond the same horizon. If we don’t take such a move into consideration, we could walk into a trap.
These kinds of problems necessarily follow whenever we don’t have the entire game tree available to us for analysis and decision-making.
So what’s to be done?
Heuristics and variable depth search
We can use heuristics to determine when it’s appropriate to probe more deeply if needed. One approach is called “quiescence search.”
Quiescence search
Quiescence is the state of being temporarily quiet, inactive, or at rest. Derived from the Latin word quiescere, the term is used across various disciplines to describe temporary dormancy or a suspension of activity.
– Wiktionary (https://en.wiktionary.org/wiki/quiescence)
What do we mean by quiescence? Well, for starters it’s the opposite of “active” or “volatile.” In many games we have volatile and quiescent states.
Consider these examples from chess.
Here we have a quiescent state. No pieces are particularly vulnerable, none are under attack. It would take a few more moves for the game to develop to a more active state.

Here’s a volatile state. The white pawn at d4 is under attack by the black knight at c6. The white knight at c3 is under attack by the black bishop at b4. The black pawn at d5 is under attack by the white knight at c3, but this piece is “pinned” by the black bishop—it cannot move without exposing the white king to check. The black pawn at h7 is under attack by the white queen at d3. All pieces under attack are protected. Clearly there’s a lot more going on here than in the first example. A human would likely take more time evaluating this board position and deciding on a move than they would in the first example.

Here’s another example of a volatile state. White’s bishop at g5 is attacking the knight on f6. If the knight moves, the bishop will be attacking black’s queen. The pawn at e5 is also attacking the knight. If the bishop were to capture the knight, the bishop would then be attacking black’s queen, and the pawn at e5 would protect the bishop. White and black rooks are in a face-off, but if black were to capture white’s rook on d1 this would result in check, since the white king is on e1. However, white could respond by capturing the rook with the bishop on e2. There’s a lot going on here.

Zugzwang (what a wonderful word!) is German for “a compulsion to move” and in chess and other board games that do not allow a player to “pass” it’s given to game states where the player’s best move would be to pass—if only they could. Instead, the player is forced to move by the rules of the game and any move will leave the player in a worse position. Consider this example from Fischer vs Taimanov (1971), with black to move. Black’s king is the knight’s only protection. If black moves the king, they lose the knight. If they move the knight, then the passed pawn could advance (if a pawn reaches the opponent’s back rank it is promoted to another piece, usually but not always a queen). So it doesn’t matter what black does. They must do something and whatever they do, they’ll be worse off for it.

We could devise a heuristic that classifies game states into “volatile” or “quiescent” and in the volatile case, we’d let the engine probe a little more deeply into the game tree than it would otherwise. One approach is to continue into the game tree and stop along each branch when a quiescent state is reached. This would lead to a variable depth probing not unlike what we’ve seen with \idastar.
Of course, there’s more than a little subtlety in how search and evaluation might proceed. As complicated as this might be for chess, the problem may be even more complicated in games like go or reversi, where multiple captures can occur at once or game pieces can change color! The general approach is the same: identify volatile states and give them more consideration than would be given to a quiescent state.
Null-move heuristic
Another approach that’s intended to improve play is called the null-move heuristic. We’ve seen how \alpha–\beta can be used to reduce time needed to determine the best move for a given player. The purpose of the null-move heuristic is to identify pruning opportunities at a shallower depth than would be needed otherwise. The quicker we can prune, the quicker our search.
The idea is simple. We evaluate the game tree as if the current player took a pass—a null move. If the algorithm identifies a pruning opportunity given the null move, then it’s reasonable to assume that the same pruning opportunity would exist if the current player did take their move.
In employing the null-move heuristic, the computer program first forfeits the turn of the side whose turn it is to move, and then performs an alpha–beta search on the resulting position to a shallower depth than it would have searched the current position had it not used the null move heuristic. If this shallow search produces a cutoff, it assumes the full-depth search in the absence of a forfeited turn would also have produced a cutoff. Because a shallow search is faster than deeper search, the cutoff is found faster… If the shallow search fails to produce a cutoff, then the program must make the full-depth search.
– Wikipedia (https://en.wikipedia.org/wiki/Null-move_heuristic)
Now there are some crucial assumptions here. First and foremost, that moves by any player generally improve that player’s position. Under the assumption of optimal play, this is quite reasonable. However, we’ve already seen a scenario where this is not the case: zugzwang. In the absence of zugzwang, this is a fair assumption.
The other assumption is that this approach will identify pruning opportunities that would exist if the null-move hadn’t taken place. But alas this is not always the case. Sometimes, we use the null-move and we don’t find a pruning opportunity. In this case, the search would run again without the null-move. In practice, however, it’s often the case that we can find pruning opportunities earlier and often enough that the additional cost of running the search again is effectively amortized over many cases where we can prune early. If this feels a lot like caching, there’s a reason. Recall that a cache miss forces us to dig deeper into the memory hierarchy at the cost of additional clock cycles—usually a great many clock cycles. But that’s OK if we get cache hits with sufficient frequency to make it worth our while. A similar dynamic is at work with the null-move heuristic: if it works with sufficient frequency, thereby allowing us to prune more efficiently, the occasional miss doesn’t ruin performance.
However, we’d need a good way of identifying zugzwang and similar cases before applying the null-move heuristic. In chess, most engines will not use the null-move if the player to move is in check or has only a small number of pieces remaining.
Copyright © 2023–2026 Clayton Cafiero
No generative AI was used in producing drafts of this material. This was written the old-fashioned way. AI was used to rewrite existing pseudocode in LaTeX to produce standalone *.tex files for rendering, and for revisions toward satisfying WCAG 2.1 AA-level accessibility standards as required by UVM policy. It may also have been used to proofread selected human-written prose. Claude 2.1.150 with model Sonnet 4.6. Revisions, if any, were performed by the author. AI was not used in generating any code whatsoever. All code samples and starter code are by the author only.