Overview
Backtracking builds solutions one choice at a time and abandons (backtracks from) a partial solution as soon as it cannot lead to a valid answer. It is depth-first search over a tree of decisions: each level is a decision, each branch is an option, and each leaf is a complete candidate.
Every backtracking solution follows the same three-step template: choose an option, explore recursively, then un-choose (undo the change) so the next option starts from a clean state. Pruning, meaning cutting branches early when constraints are already violated, is what turns exponential brute force into something practical.
You try the first key. If it does not fit, you take it out (undo) and try the next. For a series of locked doors, you only move to the next door after the current one opens, and you step back to try a different key on an earlier door when you get stuck.
When to use it
- The problem asks for all solutions: all subsets, permutations, combinations, paths, or arrangements.
- Constraint satisfaction: Sudoku, N-Queens, word search, crosswords.
- Input size is small (n around 10 to 20), a strong hint that exponential time is expected.
- You can check partway through whether a partial solution is still valid (pruning).
Problem patterns it solves
Recognize it when: all subsets, power set, subsequences.
- 78. Subsets
- 90. Subsets II
- 1863. Sum of All Subset XOR Totals
Recognize it when: all orderings, arrangements.
- 46. Permutations
- 47. Permutations II
- 784. Letter Case Permutation
Recognize it when: choose items that sum to a target, with or without reuse.
- 39. Combination Sum
- 40. Combination Sum II
- 216. Combination Sum III
- 77. Combinations
Recognize it when: split a string so every part satisfies a rule.
- 131. Palindrome Partitioning
- 93. Restore IP Addresses
- 140. Word Break II
Recognize it when: find a word or path in a grid without reusing cells.
- 79. Word Search
- 212. Word Search II
- 980. Unique Paths III
Recognize it when: place items so no constraints are violated.
- 51. N-Queens
- 37. Sudoku Solver
- 22. Generate Parentheses
- 17. Letter Combinations of a Phone Number
Where it is used in real software
Sudoku solvers, crossword generators, and chess engines explore moves recursively and undo them.
SAT solvers and constraint programming systems (scheduling, timetabling) use backtracking with sophisticated pruning.
Many regular expression engines (Perl, Java, JavaScript) backtrack when a partial match fails, which is also why some patterns can be catastrophically slow.
Generating all valid combinations of options for combinatorial testing.
Key terms
- State
- The partial solution being built, such as the current path.
- Choice
- An option added to the state at the current level.
- Undo / backtrack
- Reverse the choice after exploring it (pop, unmark, restore).
- Pruning
- Stop exploring a branch that can no longer lead to a valid answer.
- Decision tree
- The tree of all choice sequences that backtracking explores.
The universal template
- 1Define the state and the goal
What does a partial solution look like, and when is it complete?
- 2Record complete solutions
When the goal is reached, save a copy of the state.
- 3Loop over the available choices
The options at this level, often starting from an index to avoid repeats.
- 4Prune invalid choices
Skip options that break a constraint (sum too big, duplicate, conflict).
- 5Choose, explore, un-choose
Add the choice, recurse, then remove it so the state is clean for the next option.
Combination Sum: candidates [2, 3, 6, 7], target 7
Each number may be reused; start index prevents duplicate orderings
| Path | Sum | Decision |
|---|---|---|
| [2] | 2 | explore |
| [2, 2] | 4 | explore |
| [2, 2, 2] | 6 | explore; adding 2 gives 8 > 7: prune |
| [2, 2, 3] | 7 | found, record |
| [2, 3] | 5 | next 3 gives 8: prune |
| [3] | 3 | 3 + 3 = 6, then 6 + 3 = 9: prune |
| [6] | 6 | 6 + 6 = 12: prune |
| [7] | 7 | found, record |
NOWPath: [2] | Sum: 2 | Decision: explore
Results: [2, 2, 3] and [7]. Because candidates are sorted, once one option overshoots, every larger option at that level can be skipped with a break.
Implementation
function combinationSum(candidates, target) { candidates.sort((a, b) => a - b); const result = [], path = []; function backtrack(start, remaining) { if (remaining === 0) { result.push([...path]); // copy the state return; } for (let i = start; i < candidates.length; i++) { if (candidates[i] > remaining) break; // prune: sorted, so later ones are bigger path.push(candidates[i]); // choose backtrack(i, remaining - candidates[i]); // explore (i, not i + 1: reuse allowed) path.pop(); // un-choose } } backtrack(0, target); return result;} // 79. Word Search: mark cells in place, restore on returnfunction exist(board, word) { const rows = board.length, cols = board[0].length; function dfs(r, c, i) { if (i === word.length) return true; if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[i]) return false; const saved = board[r][c]; board[r][c] = "#"; const found = dfs(r + 1, c, i + 1) || dfs(r - 1, c, i + 1) || dfs(r, c + 1, i + 1) || dfs(r, c - 1, i + 1); board[r][c] = saved; return found; } for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) if (dfs(r, c, 0)) return true; return false;} // 131. Palindrome Partitioningfunction partition(s) { const result = [], path = []; const isPal = (l, r) => { while (l < r) if (s[l++] !== s[r--]) return false; return true; }; function backtrack(start) { if (start === s.length) { result.push([...path]); return; } for (let end = start; end < s.length; end++) { if (!isPal(start, end)) continue; path.push(s.slice(start, end + 1)); backtrack(end + 1); path.pop(); } } backtrack(0); return result;}Complexity and performance
2^n subsets, O(n) to copy each.
n! orderings.
Number of results times copy cost.
Recursion plus the current path, excluding output.
Trade-offs
If you only need a count or a best value and subproblems repeat, DP is exponentially faster. Backtracking is required when you must list every solution.
Sorting input and checking constraints early can cut the search tree dramatically without changing the worst case.
Variants and related techniques
Iterate masks 0..2^n - 1 to enumerate subsets without recursion.
Cache results for repeated states when you need counts (becomes DP).
Limit depth and increase it gradually; used when the solution depth is unknown.
Common mistakes
- Saving a reference to the path instead of a copy.
Fix: result.push([...path]) or new ArrayList<>(path); otherwise every saved result changes later.
- Forgetting to undo the choice.
Fix: Every push needs a matching pop; every mark needs an unmark.
- Duplicate results with repeated values.
Fix: Sort, then skip c[i] === c[i - 1] when i > start.
- Using i + 1 vs i incorrectly.
Fix: Recurse with i when reuse is allowed and i + 1 when each item is used once.
Interview questions
How do you avoid duplicate combinations when the input has duplicates?
Sort the input, and at each recursion level skip a value equal to the previous value at that level (i > start && nums[i] === nums[i - 1]). This avoids starting identical branches.
What is the difference between backtracking and DFS?
Backtracking is DFS on an implicit tree of choices, with an explicit undo step and pruning of invalid partial solutions.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 22. Generate Parentheses | Medium | Constraint-based choices. |
| 39. Combination Sum | Medium | Reuse with start index. |
| 40. Combination Sum II | Medium | Skip duplicates. |
| 79. Word Search | Medium | Grid marking and restore. |
| 131. Palindrome Partitioning | Medium | Partition strings. |
| 37. Sudoku Solver | Hard | Heavy constraint pruning. |