RECURSION & SEARCH / ALGORITHM BRIEF

Backtracking

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.

IntermediatePhase 06 / Topic 1 of 6Mental modelComplexityEdge cases
01

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.

Trying keys on a ring

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.

02

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).
03

Problem patterns it solves

Subsets (take / skip)

Recognize it when: all subsets, power set, subsequences.

  • 78. Subsets
  • 90. Subsets II
  • 1863. Sum of All Subset XOR Totals
Permutations (use each once)

Recognize it when: all orderings, arrangements.

  • 46. Permutations
  • 47. Permutations II
  • 784. Letter Case Permutation
Combinations with a target

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
Partitioning strings

Recognize it when: split a string so every part satisfies a rule.

  • 131. Palindrome Partitioning
  • 93. Restore IP Addresses
  • 140. Word Break II
Grid search with undo

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
Constraint placement

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
04

Where it is used in real software

Puzzle and game solvers

Sudoku solvers, crossword generators, and chess engines explore moves recursively and undo them.

Constraint solvers

SAT solvers and constraint programming systems (scheduling, timetabling) use backtracking with sophisticated pruning.

Regex engines

Many regular expression engines (Perl, Java, JavaScript) backtrack when a partial match fails, which is also why some patterns can be catastrophically slow.

Configuration and test generation

Generating all valid combinations of options for combinatorial testing.

05

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.
06

The universal template

  1. 1
    Define the state and the goal

    What does a partial solution look like, and when is it complete?

  2. 2
    Record complete solutions

    When the goal is reached, save a copy of the state.

  3. 3
    Loop over the available choices

    The options at this level, often starting from an index to avoid repeats.

  4. 4
    Prune invalid choices

    Skip options that break a constraint (sum too big, duplicate, conflict).

  5. 5
    Choose, explore, un-choose

    Add the choice, recurse, then remove it so the state is clean for the next option.

07

Combination Sum: candidates [2, 3, 6, 7], target 7

Each number may be reused; start index prevents duplicate orderings

Step 1 / 8
PathSumDecision
[2]2explore
[2, 2]4explore
[2, 2, 2]6explore; adding 2 gives 8 > 7: prune
[2, 2, 3]7found, record
[2, 3]5next 3 gives 8: prune
[3]33 + 3 = 6, then 6 + 3 = 9: prune
[6]66 + 6 = 12: prune
[7]7found, 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.

08

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;}
09

Complexity and performance

SubsetsO(2^n x n)

2^n subsets, O(n) to copy each.

PermutationsO(n! x n)

n! orderings.

Combinations C(n, k)O(C(n, k) x k)

Number of results times copy cost.

SpaceO(depth)

Recursion plus the current path, excluding output.

10

Trade-offs

Backtracking vs DP

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.

Pruning strength

Sorting input and checking constraints early can cut the search tree dramatically without changing the worst case.

11

Variants and related techniques

Bitmask enumeration

Iterate masks 0..2^n - 1 to enumerate subsets without recursion.

Memoized backtracking

Cache results for repeated states when you need counts (becomes DP).

Iterative deepening

Limit depth and increase it gradually; used when the solution depth is unknown.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
22. Generate ParenthesesMediumConstraint-based choices.
39. Combination SumMediumReuse with start index.
40. Combination Sum IIMediumSkip duplicates.
79. Word SearchMediumGrid marking and restore.
131. Palindrome PartitioningMediumPartition strings.
37. Sudoku SolverHardHeavy constraint pruning.