Skip to main content

Recursion & Backtracking

The Mental Model

Recursion is delegation. Instead of solving the whole problem, you solve a smaller version and combine results. Backtracking is systematic trial and error—try a choice, explore all consequences, undo the choice, try the next. Analogy: Imagine you manage a team. Instead of doing all the work yourself, you hand a slightly smaller version of the task to a subordinate, who hands an even smaller version to their subordinate, until someone gets a trivially small task and just does it. The answers bubble back up the chain. That is recursion. Backtracking adds: “if the subordinate reports failure, try a different subordinate.”
Pattern Recognition Signals:
  • “Generate all combinations/permutations”
  • “Find all valid configurations”
  • Problem has natural substructure (solve for n-1, then n)
  • Constraints are small (n ≤ 20 for backtracking, n ≤ 10 for permutations)

When to Use

Recursion

  • Tree/graph traversals
  • Divide and conquer
  • Problems with substructure
  • DP (memoized recursion)

Backtracking

  • Generate combinations/subsets
  • Permutations
  • Constraint satisfaction (Sudoku, N-Queens)
  • Path finding with constraints

The Recursion Framework

Every recursive function has three parts:

Pattern 1: Subsets (Power Set)

Problem: Generate all 2^n subsets of an array. Approach: For each element, make two choices: include it or don’t. Analogy: Imagine standing at a buffet with n dishes. At each dish, you decide “take it” or “skip it.” Every possible combination of takes and skips is a subset. With n dishes, you make n binary decisions, giving 2^n total subsets. Worked example: nums = [1, 2, 3]
Time Complexity: O(2ⁿ) subsets, O(n) to copy each = O(n × 2ⁿ) Codeforces Problems:

Pattern 2: Permutations

Problem: Generate all n! permutations. Approach: Fix each element at the current position, recurse for the rest.
Alternative: Using visited array

Pattern 3: Combinations

Problem: Generate all C(n, k) combinations of size k. Key Insight: Like subsets, but only output when we have exactly k elements. The pruning line is critical: current.size() + (n - start + 1) < k checks whether there are enough remaining elements to fill the combination. Without this, you explore branches that can never lead to a valid combination — wasting exponential time. For example, if you need 5 elements but only 3 remain, there is no point continuing.

Pattern 4: Constraint Satisfaction

Problem: N-Queens - Place N queens on NxN board so none attack each other. Approach: Place queens row by row, check constraints before placing. Why row-by-row? Since no two queens can share a row, there is exactly one queen per row. This reduces the search space from C(n^2, n) positions to n^n positions (one column choice per row), and with constraint checking, most branches are pruned immediately. Diagonal indexing trick: For an NxN board, cells on the same ”/” diagonal have the same row + col value, and cells on the same "" diagonal have the same row - col value. We add n to row - col to keep indices non-negative.

Pattern 5: Path Finding with Backtracking

Problem: Find all paths from start to end in a graph/grid.

The Backtracking Template


Pruning: The Key to Efficiency

Backtracking can be slow (exponential). Pruning cuts branches early. Analogy: Think of exploring a maze. Without pruning, you explore every dead end fully before backtracking. With pruning, you notice “this corridor is a dead end” before walking all the way in. The earlier you prune, the more time you save. In contest problems, good pruning can turn a TLE into an AC on the same algorithm.

Types of Pruning

  1. Constraint Pruning: Skip choices that violate constraints (e.g., queen attacks another queen)
  2. Bound Pruning: Skip if current path cannot lead to a better solution than the best so far
  3. Symmetry Pruning: Skip symmetric configurations (e.g., in N-Queens, only check half the first-row positions)

Common Mistakes

Mistake 1: Forgetting to Backtrack
Mistake 2: Stack Overflow Deep recursion (depth > 10⁴) can overflow the stack.
  • Use iterative approach
  • Increase stack size: ulimit -s unlimited (Linux)
  • For Codeforces, recursion depth > 10⁵ often fails
Mistake 3: Wrong Base Case The base case must handle the smallest valid input:

Recursion vs Iteration

Rule of Thumb: Use recursion for trees/graphs, backtracking, and when natural. Convert to iteration if stack overflow is a concern.
Contest tip: On Codeforces, the default stack size is about 256 KB. With each recursive call using roughly 100-200 bytes of stack, you can safely recurse to depth ~10^4. For deeper recursion (graphs with 10^5+ nodes), either use iterative DFS or add #pragma comment(linker, "/STACK:1000000000") on Windows, or submit with a custom stack size on Linux-based judges.

Practice Problems

Beginner (800-1100)

Intermediate (1100-1400)

Advanced (1400-1700)


Key Takeaways

Three Parts

Every recursion: base case, recursive case, combine results.

Backtrack = Undo

After recursing, restore state to explore other branches.

Prune Early

Skip invalid branches as soon as possible to avoid TLE.

Watch the Stack

Recursion depth > 10⁴ risks stack overflow on most judges.

Next Up

Chapter 9: Greedy Algorithms

Learn when local optimal choices lead to global optimal solutions—and when they don’t.