name: Backtracking
slug: backtracking
difficulty_level: 3
pattern_type: algorithm
display_order: 10

description: >
  Build solutions incrementally, exploring choices one at a time and abandoning
  paths that fail to satisfy constraints. Backtracking systematically explores
  all possibilities by making a choice, recursing, then undoing the choice.

when_to_use: |
  - Generating all permutations or combinations
  - Constraint satisfaction (Sudoku, N-Queens)
  - Subset/partition problems
  - Path finding with constraints
  - Word search in grids

metaphor: |
  Imagine navigating a maze by always trying the left path first. When you hit
  a dead end, you backtrack to the last intersection and try the next option.
  You systematically explore every possible route, backing up whenever you reach
  an invalid state.

  Another analogy: filling out a Sudoku puzzle. You try a number in an empty cell.
  If it leads to a contradiction later, you erase it (backtrack) and try the next
  number. If no number works, you backtrack further to a previous cell.

core_concept: |
  Backtracking is a refined brute force that prunes invalid branches early.
  The pattern follows a template:

  1. **Choose**: Make a choice (add element to path, place a queen, etc.)
  2. **Explore**: Recurse to explore consequences of that choice
  3. **Unchoose**: Undo the choice (backtrack) to try alternatives

  The key insight is that we build a **decision tree** where each node represents
  a state and edges represent choices. Backtracking is a DFS of this tree, with
  pruning of subtrees that can't lead to valid solutions.

  **Pruning** is what makes backtracking efficient. By detecting invalid states
  early, we avoid exploring exponentially many useless branches.

visualization: |
  **Generating subsets of [1, 2, 3]:**

  ```
  Decision tree (include or exclude each element):

                    []
                  /    \
               [1]      []
              /   \    /   \
          [1,2]  [1]  [2]   []
          /  \   / \  / \   / \
    [1,2,3][1,2][1,3][1][2,3][2][3][]

  Subsets: [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]
  ```

  **N-Queens (N=4) with pruning:**

  ```
  Place queens row by row, checking column and diagonal conflicts:

  Row 0: Try col 0
    Row 1: col 0 ❌ (same col)
           col 1 ❌ (diagonal)
           col 2 ✓
      Row 2: col 0 ❌ (diagonal)
             col 1 ❌ (col 1 attacks via diagonal)
             col 2 ❌ (same col)
             col 3 ❌ (diagonal)
      Backtrack!
    Row 1: col 3 ✓
      Row 2: col 1 ✓
        Row 3: col 0 ❌
               col 1 ❌
               col 2 ❌
               col 3 ❌
        Backtrack!
      ...

  Eventually find: [1, 3, 0, 2] (queens at cols 1,3,0,2 for rows 0,1,2,3)
  ```

  **Template visualisation:**

  ```
  backtrack(state):
      if is_solution(state):
          record_solution(state)
          return

      for choice in get_choices(state):
          if is_valid(choice, state):     ← PRUNE
              make_choice(choice, state)   ← CHOOSE
              backtrack(state)             ← EXPLORE
              undo_choice(choice, state)   ← UNCHOOSE
  ```

code_template: |
  def generate_subsets(nums: list[int]) -> list[list[int]]:
      """Generate all subsets (power set)."""
      result = []

      def backtrack(start: int, path: list[int]):
          result.append(path[:])  # Record current subset

          for i in range(start, len(nums)):
              path.append(nums[i])      # Choose
              backtrack(i + 1, path)    # Explore
              path.pop()                # Unchoose

      backtrack(0, [])
      return result


  def generate_permutations(nums: list[int]) -> list[list[int]]:
      """Generate all permutations."""
      result = []
      used = [False] * len(nums)

      def backtrack(path: list[int]):
          if len(path) == len(nums):
              result.append(path[:])
              return

          for i in range(len(nums)):
              if used[i]:
                  continue

              used[i] = True            # Choose
              path.append(nums[i])
              backtrack(path)           # Explore
              path.pop()                # Unchoose
              used[i] = False

      backtrack([])
      return result


  def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
      """Find combinations that sum to target (can reuse elements)."""
      result = []

      def backtrack(start: int, path: list[int], remaining: int):
          if remaining == 0:
              result.append(path[:])
              return

          for i in range(start, len(candidates)):
              if candidates[i] > remaining:  # Prune
                  continue

              path.append(candidates[i])
              backtrack(i, path, remaining - candidates[i])  # Can reuse
              path.pop()

      candidates.sort()  # Enable pruning
      backtrack(0, [], target)
      return result


  def solve_n_queens(n: int) -> list[list[str]]:
      """Find all valid N-Queens solutions."""
      result = []
      cols = set()
      diag1 = set()  # row - col
      diag2 = set()  # row + col

      def backtrack(row: int, queens: list[int]):
          if row == n:
              # Convert to board representation
              board = []
              for c in queens:
                  board.append('.' * c + 'Q' + '.' * (n - c - 1))
              result.append(board)
              return

          for col in range(n):
              if col in cols or (row - col) in diag1 or (row + col) in diag2:
                  continue  # Prune: under attack

              # Choose
              cols.add(col)
              diag1.add(row - col)
              diag2.add(row + col)
              queens.append(col)

              backtrack(row + 1, queens)  # Explore

              # Unchoose
              queens.pop()
              cols.remove(col)
              diag1.remove(row - col)
              diag2.remove(row + col)

      backtrack(0, [])
      return result


  def word_search(board: list[list[str]], word: str) -> bool:
      """Check if word exists in grid following adjacent cells."""
      rows, cols = len(board), len(board[0])

      def backtrack(r: int, c: int, i: int) -> bool:
          if i == len(word):
              return True

          if (r < 0 or r >= rows or c < 0 or c >= cols
              or board[r][c] != word[i]):
              return False

          # Choose: mark as visited
          temp = board[r][c]
          board[r][c] = '#'

          # Explore neighbors
          found = (backtrack(r + 1, c, i + 1) or
                   backtrack(r - 1, c, i + 1) or
                   backtrack(r, c + 1, i + 1) or
                   backtrack(r, c - 1, i + 1))

          # Unchoose: restore
          board[r][c] = temp

          return found

      for r in range(rows):
          for c in range(cols):
              if backtrack(r, c, 0):
                  return True

      return False

recognition_signals:
  - "all permutations"
  - "all combinations"
  - "all subsets"
  - "generate all"
  - "N-Queens"
  - "Sudoku"
  - "word search"
  - "partition"
  - "constraint satisfaction"
  - "valid arrangements"

common_mistakes:
  - title: Forgetting to unchoose (backtrack)
    description: |
      Not undoing the choice after exploring leaves state modified, causing
      incorrect results for subsequent branches.
    fix: |
      Always pair choose with unchoose:
      ```python
      path.append(choice)      # Choose
      backtrack(...)           # Explore
      path.pop()               # Unchoose - MUST DO!
      ```

  - title: Modifying state without copying
    description: |
      Adding the same path object to results multiple times—they all reference
      the same list that keeps changing.
    fix: |
      Copy the path when recording:
      ```python
      result.append(path[:])  # Shallow copy
      # or
      result.append(list(path))
      ```

  - title: Not pruning effectively
    description: |
      Checking validity only at leaf nodes means exploring many invalid
      branches that could have been cut early.
    fix: |
      Validate as early as possible:
      ```python
      for choice in choices:
          if is_valid(choice):  # Prune BEFORE recursing
              make_choice(choice)
              backtrack(...)
      ```

  - title: Wrong base case
    description: |
      Recording partial solutions as complete, or not recognizing when a
      complete solution is reached.
    fix: |
      Clearly define what constitutes a complete solution:
      - Permutations: path length equals input length
      - Subsets: (all indices are complete solutions)
      - N-Queens: placed N queens

variations:
  - name: Subsets
    description: |
      Generate all subsets. Each element is either included or not. Record
      at every node, not just leaves.
    example: "Subsets, Subsets II (with duplicates)"

  - name: Permutations
    description: |
      Generate all orderings. Track which elements are used. Record only at
      leaves (when all elements used).
    example: "Permutations, Permutations II"

  - name: Combinations
    description: |
      Generate subsets of specific size k, or combinations that meet a target
      sum. Use start index to avoid duplicates.
    example: "Combinations, Combination Sum"

  - name: Constraint satisfaction
    description: |
      Place elements satisfying constraints (non-attacking queens, valid
      Sudoku). Heavy pruning based on constraints.
    example: "N-Queens, Sudoku Solver"

  - name: Path finding
    description: |
      Find paths in grids or graphs, marking visited cells temporarily.
      Unmark when backtracking.
    example: "Word Search, Unique Paths III"

related_patterns:
  - dfs
  - dynamic-programming

prerequisite_patterns:
  - dfs
