AlgoScope

Backtracking

algorithmintermediateTime O(n!) worst case, far less with pruningSpace O(depth)

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.

012012111101001

Find a path from the top-left to the bottom-right through open cells (1), never through walls (0). Walk depth-first, trying down, right, up, left in that order, and retreat from any cell with no way forward.

Check your understanding

The player pauses before each decision in this run and asks what happens next. Here are all 6, with their answers.

  1. At (0, 0), trying down, right, up, left. Where next?

    • down
    • right
    • up
    • Dead end, retreat

    Answer: down. down is the first direction in order that leads to an open, unvisited cell.

  2. At (1, 0), trying down, right, up, left. Where next?

    • down
    • right
    • up
    • Dead end, retreat

    Answer: Dead end, retreat. Nothing open remains around it. Backtracking drops it from the path.

  3. At (0, 0), trying down, right, up, left. Where next?

    • down
    • right
    • up
    • Dead end, retreat

    Answer: right. right is the first direction in order that leads to an open, unvisited cell.

  4. At (0, 1), trying down, right, up, left. Where next?

    • down
    • right
    • up
    • Dead end, retreat

    Answer: right. right is the first direction in order that leads to an open, unvisited cell.

  5. At (0, 2), trying down, right, up, left. Where next?

    • down
    • right
    • up
    • Dead end, retreat

    Answer: down. down is the first direction in order that leads to an open, unvisited cell.

  6. At (1, 2), trying down, right, up, left. Where next?

    • down
    • right
    • up
    • Dead end, retreat

    Answer: down. down is the first direction in order that leads to an open, unvisited cell.

How it runs, step by step

  1. Find a path from the top-left to the bottom-right through open cells (1), never through walls (0). Walk depth-first, trying down, right, up, left in that order, and retreat from any cell with no way forward.

    Solving a 3 by 3 maze by depth-first search with backtracking.

  2. At (0, 0). down is open: step to (1, 0).

    Step down to 1, 0.

  3. At (1, 0) every direction is a wall, the edge, or already visited: a dead end. Retreat to (0, 0) and try its next direction.

    Dead end at 1, 0. The path retreats.

  4. At (0, 0). right is open: step to (0, 1).

    Step right to 0, 1.

  5. At (0, 1). down: wall, outside or already visited. right is open: step to (0, 2).

    Step right to 0, 2.

  6. At (0, 2). down is open: step to (1, 2).

    Step down to 1, 2.

  7. At (1, 2). down is open: step to (2, 2). That is the exit.

    Step down to 2, 2.

  8. Exit reached in 4 moves along (0,0) (0,1) (0,2) (1,2) (2,2), after 1 retreat. Marking cells as visited is what keeps the search finite; the retreats are what make it complete.

    The exit was reached.

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.

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

Next up