Backtracking
Backtracking performs depth-first search over a decision tree. It makes a choice, updates state, explores, and restores the exact previous state before trying the next choice.
def search(state):
if is_solution(state):
record(state.copy())
return
for choice in candidates(state):
if not feasible(state, choice):
continue
apply(state, choice)
search(state)
undo(state, choice)
Design checklist
- Define what one search-tree level means.
- Decide whether choices are positions, values, edges, or assignments.
- Make the solution condition and output ownership explicit.
- Pair every mutation with an exact undo, including early-return paths.
- Add only pruning rules proven unable to remove a valid required solution.
Pruning types
- Feasibility: a constraint is already violated.
- Symmetry: equivalent choices would generate duplicate states.
- Bounds: even the best possible completion cannot improve the incumbent.
- Memoization: the same residual state has already been solved; this begins to overlap with dynamic programming.
Complexity
Worst-case time is often exponential or factorial because output or search-space size is itself that large. Pruning improves explored instances, not necessarily the worst-case class. Stack depth is usually proportional to the number of decisions, excluding stored outputs.
Use backtracking when a witness or complete enumeration is required and the constraints can reject partial assignments early.