---
title: Backtracking
description: 'Backtracking is a depth-first search technique that builds a solution step by step and abandons (undoes) a partial choice as soon as it can’t lead to a valid complete solution.'
hidden: true
---

## What it is

Backtracking explores possible paths to a solution one step at a time. When a path reaches a dead end, you undo the last step and try a different option. Think of every problem as a decision tree. The root is an empty answer. Each level is a decision. Each branch is one option.

## When to use it

Use backtracking when you must generate **all** valid solutions or evaluate **all** combinations. Do not use it when you need only one solution or the optimal solution. For those, use greedy or dynamic programming. Backtracking has exponential time complexity.

## The four components

Every backtracking problem has the same parts:

1. **Base case** — when to save the current path and stop.
2. **Choices** — the options at each step.
3. **Constraints** — the rules that reject an invalid path early.
4. **Backtrack step** — undo the last choice and try the next one.

## The template

```python
def solve(data):
    result = []
    path = []

    def backtrack(index):
        # 1. Base case
        if is_complete(index, path):
            result.append(path[:])  # copy — lists are passed by reference
            return

        # 2. Choices
        for choice in get_choices(index, data):
            # 3. Constraints
            if not is_valid(choice, path):
                continue

            path.append(choice)    # make the choice
            backtrack(index + 1)   # explore
            path.pop()             # 4. Backtrack

    backtrack(0)
    return result
```

## Steps to apply the template

1. Draw the decision tree for a small input. Name each level and each branch.
2. Write the base case. Ask: when is the path complete?
3. Write the choices. Ask: what can I add at this level?
4. Write the constraints. Ask: what makes a path invalid? Reject it before recursion.
5. Add the backtrack step. Pop what you appended.
6. Copy the path when you save it. Never append the same list reference.

## Four problems, one template

### Subsets

Choice: include or skip each number. No constraint. Base case: index equals list length.

```python
def subsets(nums):
    result, path = [], []

    def backtrack(i):
        if i == len(nums):
            result.append(path[:])
            return
        path.append(nums[i])   # include
        backtrack(i + 1)
        path.pop()
        backtrack(i + 1)       # skip

    backtrack(0)
    return result
```

### Permutations

Same choices as subsets. Constraint: do not reuse a number already in the path. Base case: path length equals list length.

```python
def permute(nums):
    result, path = [], []
    used = [False] * len(nums)

    def backtrack():
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(len(nums)):
            if used[i]:
                continue
            used[i] = True
            path.append(nums[i])
            backtrack()
            path.pop()
            used[i] = False

    backtrack()
    return result
```

### Combinations

Same as subsets. Change one line: save the path only when its length equals `k`.

```python
def combine(n, k):
    result, path = [], []

    def backtrack(start):
        if len(path) == k:
            result.append(path[:])
            return
        for i in range(start, n + 1):
            path.append(i)
            backtrack(i + 1)
            path.pop()

    backtrack(1)
    return result
```

### N-Queens

Choice: which column to place the queen in the current row. Constraint: no shared column or diagonal. Base case: `n` queens placed. Backtrack: remove the last queen.

```python
def solve_n_queens(n):
    result, cols = [], []
    used_cols, diag1, diag2 = set(), set(), set()

    def backtrack(row):
        if row == n:
            result.append(cols[:])
            return
        for col in range(n):
            if col in used_cols or row - col in diag1 or row + col in diag2:
                continue
            cols.append(col)
            used_cols.add(col); diag1.add(row - col); diag2.add(row + col)
            backtrack(row + 1)
            cols.pop()
            used_cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)

    backtrack(0)
    return result
```

## Common mistakes

- You append the `path` list itself instead of a copy. The result fills with empty lists.
- You forget to pop. The path grows and never resets.
- You check constraints after recursion instead of before. You waste time on dead paths.
- You use backtracking when you need only one answer. Use greedy or DP instead.


