Unit 1 · Algorithms · Lesson 11 of 24
Recursion and backtracking
Step 1: Learn the ideas
- Recursive contracts, base cases and induction
- Recursion trees and the cost of recursive calls
- Decision trees: include/exclude versus choose-next
- Subsets, combinations and permutations
- Duplicate handling with sorting and sibling skipping
- Pruning with constraints and bounds
- Grid search with temporary marking
- Copying the current path versus sharing it
- Recursion depth limits and explicit stacks
- From backtracking to memoization
Why it matters
Many problems are naturally recursive: trees, nested structures, divide and conquer, and every "generate all valid configurations" task. Writing recursion well means specifying exactly what a call returns and then trusting it for smaller inputs. Backtracking is recursion over a tree of decisions with undo; it is how constraint solvers, parsers and search procedures work. In interviews it tests clarity about state and honest complexity analysis, since the output itself is often exponential.
Core ideas
Recursion as induction. Write the contract first, for example "height(node) returns the number of nodes on the longest downward path from node, and 0 for an empty tree". Handle the base case, then assume the contract holds for smaller inputs. Correctness is an induction on input size; termination needs every call to move toward a base case. MIT 6.006 classifies recursive algorithms by the shape of their call graph: a star for brute force, a chain for decrease and conquer, a tree for divide and conquer, and a DAG with shared subproblems for dynamic programming.
Cost. Draw the recursion tree and add up the work in its nodes. Space is the maximum depth times the frame size, plus anything else allocated. Passing copies (path + [x], string slices) adds O(length) per call; sharing one mutable path and undoing changes avoids that.
Pattern: Backtracking search.
def backtrack(state):
if is_complete(state):
record(copy_of(state))
return
for choice in choices(state):
if not feasible(state, choice):
continue # prune
apply(choice, state)
backtrack(state)
undo(choice, state)
Invariant: state is identical on entry and exit; only the recursive call sees the applied choice. The two classic bugs are forgetting to undo and recording a reference to the mutable path instead of a copy. In Go, appending to a shared slice can also overwrite results you already stored.
Decision-tree shapes.
- Subsets: at index i, include or exclude
a[i]; 2ⁿ leaves, and copying each subset makes it O(n × 2ⁿ). - Combinations and combination sums: choose the next element from index
startonward so each set appears once in a canonical order; recurse withiwhen reuse is allowed andi + 1when it is not. - Permutations: choose any unused element; n! leaves, O(n × n!) with copying.
- Duplicate values: sort first, then at the same depth skip a value equal to its previous sibling (
i > start and a[i] == a[i - 1]).
Pruning. Cut branches that cannot succeed: stop when the remaining target goes negative (with sorted positive candidates you can break out of the loop), track used columns and diagonals in N-Queens, or add a closing parenthesis only while one is still open. Pruning rarely improves the worst-case bound, but it can shrink the explored tree dramatically.
Grids. For path search in a grid (Word Search), mark a cell as visited before recursing and restore it afterwards, so one path never reuses a cell while other paths still can.
When it becomes dynamic programming. If the same subproblem (say, the same remaining index and remaining target) recurs and its answer does not depend on how you reached it, memoize it. If the task is to list every configuration, memoization helps little: the output size is the cost.
Common misconceptions
- "Recursion is risky, so avoid it." It is often the clearest correct solution; know the depth and your language's limit, and convert to an explicit stack when depth can reach tens of thousands.
- "Backtracking is O(2ⁿ)." State the bound from the decision tree and the output, for example O(n × 2ⁿ) for subsets or O(n × n!) for permutations; copying counts.
- "Recording the current path is enough." Record a copy; the shared path keeps changing as the search continues.
- "A global visited set prevents duplicate outputs." Duplicates caused by repeated input values need sorting and sibling skipping; a global visited set can wrongly block valid paths.
- "Pruning improves the worst case." Usually it improves typical behavior only; say so when asked.
How to approach it
Before coding, name the state, the choices at each step, the completion test and the pruning rule. Draw the first two levels of the decision tree for a tiny input and count leaves to get the bound. Test an empty input and an input with duplicates. Interviewers typically probe the complexity of exponential outputs, duplicate handling and whether memoization applies. Practicing the template on varied problems transfers to new ones; memorizing particular question lists does not.
Suggested order
- 1Read Chapter 1 (Recursion) of Jeff Erickson’s Algorithms, especially the sections on reductions, simplify and delegate, and recursion trees.
- 2Read MIT 6.006 Lecture 15 (Recursive Algorithms) for the classification of recursive algorithms by call-graph shape and the SRT BOT framework.
- 3Read Chapter 2 (Backtracking) of Erickson’s Algorithms, focusing on N Queens, Subset Sum and The General Pattern.
- 4Solve Subsets and Permutations with the choose, recurse, undo template and state the O(n · 2^n) and O(n · n!) bounds.
- 5Solve Combination Sum, then Subsets II and Combination Sum II to practice the start index and sibling skipping.
- 6Solve Letter Combinations of a Phone Number and Generate Parentheses, pruning invalid prefixes early.
- 7Solve Word Search with temporary marking, then Palindrome Partitioning and N-Queens as harder review problems.
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
Algorithms (Jeff Erickson): Recursion and Backtracking chapters, with amortized-analysis notesStart here
Jeff Erickson, University of Illinois Urbana-ChampaignSection: Ch. 1: Recursion
Recursion as reduction to smaller instances, and analyzing it with recursion trees.
Open(opens in a new tab)Link responded, 5 Oct 2026Algorithms (Jeff Erickson): Recursion and Backtracking chapters, with amortized-analysis notesStart here
Jeff Erickson, University of Illinois Urbana-ChampaignSection: Ch. 2: Backtracking
N Queens, subset sum and the general backtracking pattern, with careful running-time analysis.
Open(opens in a new tab)Link responded, 5 Oct 2026MIT 6.006 Introduction to Algorithms (Spring 2020)
MIT OpenCourseWareSection: Lecture 15: Recursive Algorithms (Dynamic Programming, Part 1)
The SRT BOT framework and the classification of recursive algorithms (brute force, decrease and conquer, divide and conquer, dynamic programming) by call-graph shape.
Open(opens in a new tab)Link responded, 5 Oct 2026Competitive Programmer’s Handbook: search, amortized-analysis, range-query and trie sections
CSESSection: Ch. 5: Complete search
Generating subsets and permutations, backtracking and pruning, in compact code.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
For every problem, write the state, the choices, the completion test and the pruning rule before coding, and draw the first two levels of the decision tree. Always record a copy of the current path. Subsets II and Combination Sum II are the duplicate-handling checks, Word Search checks marking and restoring, and Palindrome Partitioning and N-Queens are review problems for cut-position search and constraint pruning.
- SubsetsLeetCode(opens in a new tab)Not attemptedLeetCode
- PermutationsLeetCode(opens in a new tab)Not attemptedLeetCode
- Combination SumLeetCode(opens in a new tab)Not attemptedLeetCode
- Subsets IILeetCode(opens in a new tab)Not attemptedLeetCode
- Combination Sum IILeetCode(opens in a new tab)Not attemptedLeetCode
- Letter Combinations of a Phone NumberLeetCode(opens in a new tab)Not attemptedLeetCode
- Generate ParenthesesLeetCode(opens in a new tab)Not attemptedLeetCode
- Word SearchLeetCode(opens in a new tab)Not attemptedLeetCode
- Palindrome PartitioningLeetCode(opens in a new tab)Not attemptedLeetCode
- N-QueensLeetCode(opens in a new tab)Not attemptedLeetCode
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1What is the precise contract of your recursive function, and what is its base case?
- Question 2Why must you record a copy of the path rather than the path itself?
- Question 3How does a start index prevent duplicate combinations, and how does sibling skipping handle duplicate values?
- Question 4What is the honest time bound for generating all permutations, including output?
- Question 5What does pruning change about the worst case, and what does it not change?
- Question 6When does a recursive search have overlapping subproblems worth memoizing?
You’re done when you can
- Write subset, permutation and combination generators from memory, with correct bounds, in under 10 minutes each.
- Solve one unseen backtracking problem with duplicate inputs in under 30 minutes without producing duplicate outputs.
- Explain the recursion-depth limit of your language and convert one recursive solution to an explicit stack.
- Say, for a given recursive search, whether memoization would help and why.
Finish the lesson
Unlocks next: Trees and binary search trees, Graph traversal: BFS, DFS and components, Divide and conquer, Dynamic programming foundations


