Backtracking
Build a solution one choice at a time. Before each choice, check it against what is already placed; if nothing works, undo the last choice and try its next option. That undo is the whole trick: it turns a blind enumeration into a search that abandons a dead branch the moment it is dead. Sudoku is the same loop with a richer constraint: at each empty cell try the digits in order, keep the first that is absent from its row, column and box, and when nothing fits, clear the cell and hand the previous one its next digit.
Does "abcced" appear in the grid as a path of neighbouring cells, each used once? Start from every cell holding 'a' and extend the path through a neighbour holding the next letter, down, right, up, left in that order. A cell with no matching free neighbour is a dead end: retreat and free it.
Check your understanding
The player pauses before each decision in this run and asks what happens next. Here are all 5, with their answers.
Next letter 'b'. Free neighbours: down 's', right 'b'. Which way?
Answer: right. right is the first direction in the fixed order whose cell holds 'b'.
Next letter 'c'. Free neighbours: down 'f', right 'c'. Which way?
Answer: right. right is the first direction in the fixed order whose cell holds 'c'.
Next letter 'c'. Free neighbours: down 'c', right 'e'. Which way?
Answer: down. down is the first direction in the fixed order whose cell holds 'c'.
Next letter 'e'. Free neighbours: down 'e', right 's', left 'f'. Which way?
Answer: down. down is the first direction in the fixed order whose cell holds 'e'.
Next letter 'd'. Free neighbours: right 'e', left 'd'. Which way?
Answer: left. left is the first direction in the fixed order whose cell holds 'd'.
How it runs, step by step
Does "abcced" appear in the grid as a path of neighbouring cells, each used once? Start from every cell holding 'a' and extend the path through a neighbour holding the next letter, down, right, up, left in that order. A cell with no matching free neighbour is a dead end: retreat and free it.
Word search for abcced in a 3 by 4 grid of letters.
(0,0) holds 'a', the first letter: start a search here. Start number 1.
Start at 0, 0.
At (0,0) with "a" matched, the next letter is 'b'. Free neighbours: down 's', right 'b'. Go right to (0,1).
Extend right to 0, 1.
At (0,1) with "ab" matched, the next letter is 'c'. Free neighbours: down 'f', right 'c'. Go right to (0,2).
Extend right to 0, 2.
At (0,2) with "abc" matched, the next letter is 'c'. Free neighbours: down 'c', right 'e'. Go down to (1,2).
Extend down to 1, 2.
At (1,2) with "abcc" matched, the next letter is 'e'. Free neighbours: down 'e', right 's', left 'f'. Go down to (2,2).
Extend down to 2, 2.
At (2,2) with "abcce" matched, the next letter is 'd'. Free neighbours: right 'e', left 'd'. Go left to (2,1).
Extend left to 2, 1.
(2,1) holds 'd', the last letter. The path spells "abcced": found.
Word found.
"abcced" found after 1 start and 0 retreats: (0,0) (0,1) (0,2) (1,2) (2,2) (2,1). Marking cells as used while they are on the path is what stops a letter being reused.
Word found.
Remember
- Choose, recurse, undo. The undo is what lets the same code try every branch.
- Check constraints as early as possible: every square ruled out before recursing prunes a whole subtree.
- Reach for it on all-solutions and constraint questions: queens, sudoku, mazes, subsets, permutations.
Topics covered
Related
Where this is used
Text processingRegular expression engines
PCRE, Java's java.util.regex and Python's re match by backtracking: at each alternation or quantifier the engine takes one option, recurses on the rest of the pattern, and rewinds the input position when that rest fails. The rewind is what makes backreferences possible at all, since the engine can reconsider how the text was split. It is also why a pattern like (a+)+b can take exponential time on a long run of a's: it is walking a tree of ways to divide the same characters.
Developer toolspip and Cargo resolving versions
Choosing package versions is a constraint problem: pick a version, check it against every requirement already fixed, and on a conflict drop back and take the next candidate. pip's resolver has worked this way since 20.3, which is what the message about looking at multiple versions of a package means - it is backtracking through a release history. Cargo's resolver is the same shape: its documentation describes picking the next version and backtracking when a conflict leaves no solution.
VerificationSAT and SMT solvers
DPLL, the basis of the CDCL solvers in use today, assigns a variable, propagates the clauses that assignment forces, and undoes the assignment when some clause becomes unsatisfiable. MiniSat and the SAT core inside Z3 add clause learning on top, so a dead branch also records the reason it died and rules out other branches carrying the same conflict. Model checkers and symbolic execution engines run on this loop.
Programming languagesProlog
In Prolog backtracking is not a technique, it is the execution model: the engine tries the first matching clause, binds variables, and on failure unwinds those bindings from a stack called the trail before trying the next clause. Typing a semicolon after an answer forces it to backtrack and produce the next one. The undo step in this lesson is that trail, written by hand.
Why it works this way
Where is the undo in the queens code?
It is there, just invisible: cols[row] = c is overwritten by the next value of c, and no earlier row ever reads that slot while a later row only reads it after it has been written fresh, so no explicit reset is needed. You can skip the undo only when the stale value is unreachable. Sudoku and word search share one grid across the whole search, so g[r][c] = 0 and used[r][c] = false are mandatory - drop either line and a failed branch leaves its guesses behind for every branch that comes after it.
Why the maze keeps its visited marks but word search clears them
Both mark a cell on the way in, and only one clears it on the way out. In the maze a cell that failed can never help again: the first visit tried every continuation from it, and the neighbours it skipped were already visited, so they were either dead too or on the path behind it. Leaving g[r][c] = 2 is a memo rather than a bug. In word search the cell's usefulness depends on the path, since the same square can match a different letter index on another route, so used[r][c] has to go back to false. The test is whether failure at that cell was path-independent.
Which choice you try first changes the running time by orders of magnitude
firstEmpty scans the sudoku grid in reading order, which is the simplest rule and one of the worst. Picking the empty cell with the fewest legal digits instead - the minimum-remaining-values heuristic - fails near the top of the tree, where a failure prunes the most. Same algorithm and same constraints, but hard puzzles drop from millions of assignments to thousands.
Collecting every solution, not just the first
All four functions here return Boolean and stop at the first success. For all solutions you do the opposite: never return early, and at the base case record a copy of the partial solution. Forgetting the copy is the classic bug - you append the live array, it keeps mutating underneath you, and you end up with a list of identical rows, usually the empty starting state.
Read more
- BacktrackingWikipedia
- Eight queens puzzleWikipedia
- Solving every sudoku puzzlePeter Norvig · norvig.com
- Dancing LinksDonald Knuth · arxiv.org
- Details of the Cloudflare outage on July 2, 2019Cloudflare
Next up
- PermutationsSwap each remaining element into the next position, recurse, and swap back; n! leaves.
- SubsetsInclude or exclude each element in turn; every leaf of that binary tree is one subset, 2^n in all.
- Branch and BoundBacktracking with a bound; prune any branch whose best possible outcome cannot beat the current best.