Phase 6 · Backtracking & SearchModule 21~40 min read

Backtracking

Build candidates incrementally and abandon them the moment they can't work — the engine behind N-Queens and Sudoku.

What you'll learn

Backtracking builds a solution incrementally and abandons a partial candidate the instant it can't possibly work — then backs up and tries something else. It's systematic brute force with early exits, and it solves puzzles that look impossibly large.

By the end you'll be able to:

  • Apply the choose / explore / un-choose template
  • Generate all subsets and permutations
  • Solve N-Queens and understand pruning

The template

Every backtracking algorithm has the same three-beat rhythm: choose an option, explore recursively, then un-choose to restore state before trying the next option. The un-choose step is what makes it "backtracking":

Language
backtrack.py
def backtrack(state):
    if is_complete(state):
        record(state)
        return
    for choice in choices(state):
        if is_valid(choice, state):
            make(choice, state)      # choose
            backtrack(state)         # explore
            undo(choice, state)      # un-choose (backtrack)

Subsets & permutations

The simplest instances just enumerate. For subsets, at each element choose to include it or not. For permutations, at each position choose an unused element. Both build a partial answer, recurse, and undo — generating all 2ⁿ subsets or n! permutations respectively.

N-Queens

Place n queens on an n×n board so none attack another. Backtracking places one queen per row, trying each column; if a column is attacked it's pruned immediately, and if a whole row has no safe column it backtracks. Step through the search on a 4×4 board — watch it try, place, hit conflicts, and back up until it lands a solution:

4-Queens by backtracking
4-Queens
trying placed conflict
1/58Solve the 4-Queens puzzle: place 4 queens so none share a row, column, or diagonal.
Try a column; place if safe; on a dead end, backtrack and try the next — until all four queens coexist.

N-Queens in code

Language
n_queens.py
def solve_n_queens(n):
    cols, diag1, diag2 = set(), set(), set()
    board, solutions = [], []
    def place(r):
        if r == n:
            solutions.append(board[:]); return
        for c in range(n):
            if c in cols or (r - c) in diag1 or (r + c) in diag2:
                continue                       # pruned: attacked
            cols.add(c); diag1.add(r - c); diag2.add(r + c); board.append(c)
            place(r + 1)                        # explore
            cols.discard(c); diag1.discard(r - c); diag2.discard(r + c); board.pop()  # undo
    place(0)
    return solutions

Tip

Tracking attacked columns and diagonals in sets makes each safety check O(1). A cell (r, c) shares a "/" diagonal when r + c is constant and a "\" diagonal when r − c is constant.

Pruning

The power of backtracking is pruning — cutting off whole branches the moment they become hopeless. Naively, 4-Queens has 4⁴ = 256 placements; pruning attacked squares explores only a handful. The tighter your validity check, the more of the search tree you skip. Sudoku is the same idea: fill the next empty cell with each legal digit, recurse, and undo on failure.

Key idea

Backtracking = DFS over a tree of partial solutions, pruning invalid branches. It's exponential in the worst case, but good pruning makes many real puzzles fast in practice.

Recap & quick check

Key takeaways

  • Backtracking builds candidates incrementally: choose, explore, un-choose.
  • The un-choose (undo) step restores state before trying the next option.
  • Subsets (include/exclude) and permutations (pick unused) are its simplest forms.
  • N-Queens places one queen per row, pruning attacked columns and diagonals.
  • Pruning invalid branches early is what makes backtracking practical.

Quick check

1. What are the three steps of the backtracking template?

2. What makes backtracking more efficient than pure brute force?

3. In N-Queens, two cells share a diagonal when:

4. Backtracking is essentially:

Backtracking finds any valid solution. To find the best one efficiently, we add bounds. Next up: Module 22 — Branch & Bound and Pruning.