Backtracking
Key idea — Backtracking is DFS on an implicit decision tree. At each step you choose a candidate, recurse to explore it, then undo (backtrack) and try the next candidate. Pruning short-circuits branches that cannot possibly lead to a valid answer.
When to use — Permutations, combinations, subsets, Sudoku/N-queens constraint satisfaction, word search on a grid. Recognise the pattern: "find all / count all / any valid arrangement."
Pattern / template
go
func backtrack(state []int, choices []int, results *[][]int) {
if isComplete(state) {
cp := make([]int, len(state))
copy(cp, state) // record a copy
*results = append(*results, cp)
return
}
for _, choice := range choices {
if isValid(state, choice) {
state = append(state, choice) // choose
backtrack(state, nextChoices(choice), results) // explore
state = state[:len(state)-1] // un-choose (backtrack)
}
}
}Complexity
- Time: O(b^d) where b = branching factor, d = depth — exponential in the worst case.
- Space: O(d) call stack + O(results) for storing solutions.
Pitfalls
- Forgetting to copy
statewhen recording a solution (you'll push the same backing slice repeatedly — usecopyinto a new slice). - Missing the pruning condition — without it the algorithm degrades to brute force.
- For permutations, track a
usedboolean array to avoid reusing the same index.
Common follow-ups
- How would you modify the template to generate combinations instead of permutations?
- Where exactly is the pruning condition applied, and how does it affect the branching factor?
- How do you avoid duplicate results when the input contains repeated elements?
- Can you convert this recursive backtracking into an iterative solution?
Practice (LeetCode)
- LC 46 — Permutations
- LC 78 — Subsets
- LC 39 — Combination Sum
- LC 22 — Generate Parentheses
- LC 51 — N-Queens