DSA · Topic 9 of 18 · Trees & Graphs

Backtracking

Choose, explore, un-choose — and stop early when a path can't work.

10 problems · Exponential: O(2^n) subsets, O(n!) permutations time, O(n) for the current path space

Step 1 Understand it

Remember it by

Choose, explore, un-choose — and stop early when a path can't work.

Watch it happen

AnimationBacktracking: choose, explore, un-choose — and prune dead ends
Read the animation as text
  1. Every subset is a list of yes/no choices. Start with the empty path []. Record it — every path is a subset, not just the full ones.
  2. Add 1: path [1]. Record it. Then try each later number: 2, 3.
  3. Add 2: path [1, 2]. Record it. Then try each later number: 3.
  4. Add 3: path [1, 2, 3]. Record it. No later numbers to add → undo the last choice and go back up.
  5. Add 3: path [1, 3]. Record it. No later numbers to add → undo the last choice and go back up.
  6. Add 2: path [2]. Record it. Then try each later number: 3.
  7. Add 3: path [2, 3]. Record it. No later numbers to add → undo the last choice and go back up.
  8. Add 3: path [3]. Record it. No later numbers to add → undo the last choice and go back up.
  9. All 8 subsets: [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]. Choose, explore, un-choose. Only trying LATER numbers stops [2, 1] repeating [1, 2].

The idea

Build a solution one choice at a time. After each choice, explore everything that follows, then undo the choice and try the next one. It walks a tree of decisions; pruning cuts off branches that can't succeed.

Picture it

flowchart TD
  R["[ ]"] -->|"take 1"| A["[1]"]
  R -->|"skip 1"| B["[ ]"]
  A -->|"take 2"| C["[1, 2]"]
  A -->|"skip 2"| D["[1]"]
  B -->|"take 2"| E["[2]"]
  B -->|"skip 2"| F["[ ]"]
Every subset of [1, 2] is a path through take/skip choices. Backtracking walks this tree depth-first.

Analogy

Walking a maze with chalk. At every fork, pick a path; at a dead end, walk back to the last fork, rub out your mark and try the next path.

Where the analogy breaks

In a maze you stop at the exit. Here you usually want every solution, so you record each one and keep exploring.

More detail

What it is

Imagine trying to find your way out of a physical maze. You pick a path, and if you hit a dead end, you physically turn around, walk back to the last intersection, and try a different path. This is Backtracking! It's a brute-force method where you exhaustively try to build a solution piece by piece. The moment you realize your current path is invalid (or you've found a full answer), you 'backtrack' (undo your last step) and try another option.

When to use it

  • When a problem asks for ALL valid combinations, permutations, or subsets.
  • When solving puzzles with specific constraints (like Sudoku or N-Queens).
  • When the input size is inherently small (N < 20).

How it works

You write a recursive function. You make a 'choice' (e.g. adding a number to a temporary list). You dive deeper by calling the recursive function. Wait for it to finish. Then, you absolutely must UNDO the choice you just made (remove the number from the list). This resets your state so you can safely try the next choice in the loop!

Example walkthrough

Input: nums = [1,2,3], find all subsets

  1. Start empty [].
  2. Add 1 -> [1]. Recurse.
  3. Add 2 -> [1, 2]. Recurse.
  4. Add 3 -> [1, 2, 3]. Recurse. (Save this solution!)
  5. Backtrack: remove 3 -> [1, 2].
  6. Backtrack: remove 2 -> [1]. Add 3 -> [1, 3]... and so on.

Result: [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

Related patterns

  • Depth-First Search (DFS): Backtracking is fundamentally just DFS on an imaginary tree of decisions.

Step 2 Recognise it

Words that signal it

  • 'All' combinations, permutations, subsets or partitions
  • 'Generate every valid …' (parentheses, palindrome splits)
  • Small input (n ≤ 15–20): exponential time is expected
  • Placing things under rules: N-Queens, Sudoku, word search on a grid

Don't confuse it with

  • Dynamic Programming: 'How many ways' or 'the best way' with repeated subproblems → DP. 'List them all' → backtracking.
  • BFS: Shortest number of steps → BFS. Every possible arrangement → backtracking.

Gut check

Do they want every possible arrangement, and n is small? → backtracking.

How to recognise it in an interview

The problem wants 'all possible combinations', 'subsets', 'permutations', or specifically mentions finding paths on a board.

Step 3 The template

The general shape. Adapt it to each problem rather than learning one solution per problem.

python
def backtrack_all(nums):
    results, path = [], []

    def backtrack(start):
        results.append(path[:])          # record a COPY (or only when complete)
        for i in range(start, len(nums)):
            # (optional) prune: skip choices that can't work
            path.append(nums[i])          # choose
            backtrack(i + 1)              # explore (use i to allow reuse)
            path.pop()                    # un-choose

    backtrack(0)
    return results
The same template in Java
text
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(result, new ArrayList<>(), nums, 0);
        return result;
    }
    
    private void backtrack(List<List<Integer>> result, List<Integer> current, int[] nums, int start) {
        // Deep copy the current list and add it to our final results
        result.add(new ArrayList<>(current));
        
        for (int i = start; i < nums.length; i++) {
            current.add(nums[i]);                  // Make a choice
            backtrack(result, current, nums, i + 1); // Explore further
            current.remove(current.size() - 1);    // Undo the choice (Backtrack!)
        }
    }
}
TimeExponential: O(2^n) subsets, O(n!) permutations
SpaceO(n) for the current path

Common mistakes

  • Appending path instead of path[:] (a copy): every saved answer then changes as path changes.
  • Forgetting to undo the choice (path.pop()) after exploring.
  • Getting duplicates: sort first and skip equal neighbours at the same depth (Subsets II, Combination Sum II).

Step 4 Watch it run

Two problems, traced step by step exactly as the code runs.

Easy example: Subsets

Input: nums = [1, 2, 3]

pathactionthen
[]record []next choices: [1, 2, 3]
[1]record [1]next choices: [2, 3]
[1, 2]record [1, 2]next choices: [3]
[1, 2, 3]record [1, 2, 3]no more choices → go back
[1, 3]record [1, 3]no more choices → go back
[2]record [2]next choices: [3]
[2, 3]record [2, 3]no more choices → go back
[3]record [3]no more choices → go back

Result: [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]

Notice: Recording at every call (not just at the leaves) is what produces all 8 subsets, including the empty one.

python
def subsets(nums):
    results, path = [], []
    def backtrack(start):
        results.append(path[:])
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1)
            path.pop()
    backtrack(0)
    return results

Medium example: Combination Sum (numbers can be reused)

Input: candidates = [2, 3, 5], target = 8

pathtotalwhat happens
[]0try [2, 3, 5] (can reuse)
[2]2try [2, 3, 5] (can reuse)
[2, 2]4try [2, 3, 5] (can reuse)
[2, 2, 2]6try [2, 3, 5] (can reuse)
[2, 2, 2, 2]8= target → record it ✓
[2, 2, 2, 3]9over target → go back ✗
[2, 2, 2, 5]11over target → go back ✗
[2, 2, 3]7try [3, 5] (can reuse)
[2, 2, 3, 3]10over target → go back ✗
[2, 2, 3, 5]12over target → go back ✗
[2, 2, 5]9over target → go back ✗
[2, 3]5try [3, 5] (can reuse)
[2, 3, 3]8= target → record it ✓
[2, 3, 5]10over target → go back ✗
[2, 5]7try [5] (can reuse)
[2, 5, 5]12over target → go back ✗
[3]3try [3, 5] (can reuse)
[3, 3]6try [3, 5] (can reuse)
[3, 3, 3]9over target → go back ✗
[3, 3, 5]11over target → go back ✗
[3, 5]8= target → record it ✓
[5]5try [5] (can reuse)
[5, 5]10over target → go back ✗

Result: [[2, 2, 2, 2], [2, 3, 3], [3, 5]]

Notice: Passing i (not i + 1) lets a number be reused; starting from i (not 0) stops [2, 3, 3] and [3, 2, 3] both appearing.

python
def combination_sum(candidates, target):
    results, path = [], []
    def backtrack(start, total):
        if total == target:
            results.append(path[:])
            return
        if total > target:
            return                         # prune
        for i in range(start, len(candidates)):
            path.append(candidates[i])
            backtrack(i, total + candidates[i])   # i: reuse allowed
            path.pop()
    backtrack(0, 0)
    return results

Step 5 Practise

Read each question and say the pattern and the trick out loud before you open the answer. Then solve it on LeetCode. Tick a problem once you've solved it on your own: it comes back for review tomorrow, then after 3, 7, 14 and 30 days, then every month, on the DSA page.

Medium

  1. Subsets Medium

    Return every subset of a list of distinct numbers.

    Show answer

    Technique: Backtracking (take or skip)

    The clue: 'All subsets'.

    The trick: Record the path at every call; for each later number, choose it, recurse, un-choose it.

  2. Combination Sum Medium

    Find all combinations (numbers can be reused) that add up to a target.

    Show answer

    Technique: Backtracking with reuse

    The clue: 'All combinations' that hit a target, reuse allowed.

    The trick: Recurse with the same index to reuse a number; stop when the total passes the target.

  3. Combination Sum II Medium

    Find all unique combinations that add up to a target; each number used at most once, and the input has duplicates.

    Show answer

    Technique: Backtracking + sort + skip duplicates

    The clue: 'All unique combinations' with repeated input values.

    The trick: Sort; at the same depth, skip a number equal to the previous one; recurse with i + 1.

  4. Permutations Medium

    Return every ordering of a list of distinct numbers.

    Show answer

    Technique: Backtracking with a used set

    The clue: 'All permutations' = every order.

    The trick: At each position try every unused number; mark it used, recurse, unmark.

  5. Subsets II Medium

    Return every subset when the input has duplicates, with no duplicate subsets.

    Show answer

    Technique: Backtracking + sort + skip duplicates

    The clue: 'All subsets' but the input repeats values.

    The trick: Sort; skip nums[i] when it equals nums[i − 1] at the same depth (i > start).

  6. Generate Parentheses Medium

    Generate every valid string of n pairs of brackets.

    Show answer

    Technique: Backtracking with counts

    The clue: 'Generate all valid' strings.

    The trick: Add '(' while open < n; add ')' while close < open.

  7. Palindrome Partitioning Medium

    Split a string into pieces so that every piece is a palindrome; return all such splits.

    Show answer

    Technique: Backtracking on cut positions

    The clue: 'All ways to split'.

    The trick: Try every end position for the next piece; only recurse when that piece is a palindrome.

  8. Letter Combinations of a Phone Number Medium

    Return every letter string a phone keypad number could stand for.

    Show answer

    Technique: Backtracking over digits

    The clue: 'All combinations', one choice per digit.

    The trick: For each digit, try each of its letters and recurse to the next digit.

Hard

  1. N Queens Hard

    Place n queens on an n × n board so that none attack each other; return all boards.

    Show answer

    Technique: Backtracking row by row with sets

    The clue: Placing items under rules, all solutions.

    The trick: Place one queen per row; track used columns and both diagonals (r − c and r + c) in sets.

What to say in the interview

'Because we need all combinations, the time complexity will naturally be exponential. I will use backtracking to explore the state-space tree.'

Follow-up prep

How do you avoid duplicate subsets if the input array has duplicate numbers?