Backtracking
Backtracking explores candidate solutions recursively, abandoning (“backtracking” on) a partial candidate the moment it can’t possibly lead to a valid one.
Why it matters
It’s the go-to method for constraint-satisfaction and combinatorial-search problems — permutations, subsets, Sudoku, N-queens, maze solving, and parsing. Backtracking systematically explores the whole solution space while pruning dead branches, which is what makes an otherwise exponential search tractable in practice.
How it works
Picture the search as a tree: each node is a partial solution, each edge a choice. Backtracking is a depth-first walk of that tree with three moves:
- Choose — extend the partial solution with one option.
- Explore — recurse to extend it further.
- Un-choose — undo the choice and try the next option.
Pruning is the whole point: a is_valid / bound check that cuts a subtree before exploring it.
function backtrack(state):
if is_complete(state): record(state); return
for choice in options(state):
if is_valid(state, choice):
apply(state, choice) # choose
backtrack(state) # explore
undo(state, choice) # un-choose
Unlike DP, the subproblems here usually don’t overlap — you’re searching, not reusing answers.
Example
N-queens — place N queens so none attack another. Go row by row; in each row try each column,
skip any column or diagonal already attacked, recurse to the next row, and undo on return.
solve(row):
if row == N: count_solution(); return
for col in 0 … N-1:
if safe(row, col):
place(row, col)
solve(row + 1)
remove(row, col)
Pitfalls
- Forgetting to undo state — leaving a choice applied corrupts sibling branches; every
chooseneeds a matchingun-choose. - No pruning — without an early-validity check you explore the full exponential tree and time out.
- Mutating shared structures without restoring them — record a copy of a complete solution, or later undos will mutate it.