Chapter 12

Recursion

High School
At a glance
Core ideaA function solves a problem by calling itself on a smaller case.
Key termBase case — the case solved directly, which stops the recursion.
You can…Write recursion with a decreasing measure and memoise it.
Watch outNo base case (or no progress) overflows the call stack.
Theory

Recursion is when a function solves a problem by calling itself on a smaller instance of the same problem. Every correct recursion has two ingredients: one or more base cases that are solved directly without further recursion, and a recursive case that reduces the problem toward a base case. Omit the base case and you get infinite recursion; fail to make progress toward it and the same.

Recursion is powered by the call stack: each call gets its own stack frame holding its local variables and its return address. Calls stack up as the recursion descends and unwind — in strict LIFO order — as each returns. This is the same stack structure from Chapter 5, now doing the language's own bookkeeping.

calls descend ↓ returns unwind ↑ factorial(3) factorial(2) factorial(1) factorial(0) = 1 ← base returns 1 1 × 1 = 1 2 × 1 = 2 3 × 2 = 6 each call keeps its own frame until it returns (LIFO)
Frames pile up as the recursion descends to the base case, then unwind last-in-first-out, multiplying on the way back.

Many powerful techniques are inherently recursive: divide and conquer (merge sort), tree and graph traversal, and problems with self-similar structure. A recurrence like T(n) = 2·T(n/2) + O(n) captures such costs and, by the Master Theorem, resolves to O(n log n).

Explanation

The trick to trusting recursion is the leap of faith: assume the function already works correctly for smaller inputs, then only make sure you (a) handle the base case and (b) combine the smaller solution correctly. If both hold, induction guarantees the whole thing is right. You do not — and should not — trace every level in your head.

Recursion and iteration are equally powerful; anything one can do, so can the other. The choice is about clarity. Tree-shaped problems read beautifully as recursion and painfully as loops-with-explicit-stacks. But recursion has a cost: each pending call consumes a stack frame, so very deep recursion can overflow the stack — where a loop would use constant space.

Insight. A recursive function is a proof by induction that happens to run. The base case is the induction basis; the recursive case is the inductive step.

Practical

Three classics: factorial (linear recursion), Fibonacci (tree recursion, with a memoised fix), and the Tower of Hanoi (exponential but elegant).

def factorial(n):
    if n == 0:              # base case
        return 1
    return n * factorial(n - 1)          # recursive case: shrink toward 0

def fib(n):                 # naive: O(2^n) -- recomputes the same values
    if n < 2:
        return n            # base cases: fib(0)=0, fib(1)=1
    return fib(n - 1) + fib(n - 2)

def fib_fast(n, memo=None):              # memoised: O(n)
    if memo is None:
        memo = {}
    if n < 2:
        return n
    if n not in memo:
        memo[n] = fib_fast(n - 1, memo) + fib_fast(n - 2, memo)   # cache result
    return memo[n]

def hanoi(n, source, target, spare):
    """Move n disks from source to target using spare, printing each move."""
    if n == 0:
        return                           # base case: nothing to move
    hanoi(n - 1, source, spare, target)  # move top n-1 out of the way
    print("move disk " + str(n) + ": " + source + " -> " + target)
    hanoi(n - 1, spare, target, source)  # move them onto the target

print(factorial(5))     # 120
print(fib_fast(30))     # 832040  (instant; naive fib(30) does ~2.7M calls)
hanoi(3, "A", "C", "B") # 7 moves = 2^3 - 1

Naive fib is a cautionary tale: it recomputes fib(2) exponentially many times. Memoisation — caching each answer the first time — collapses it from O(2ⁿ) to O(n). Hanoi shows the opposite: 2ⁿ−1 moves is optimal, an inherently exponential task expressed in four tidy lines.

Q&A
Q1 What exactly causes a "stack overflow"?

Each unreturned function call keeps a frame on the call stack, which has finite size. If recursion goes too deep — say a missing base case, or recursing 100,000 levels — the frames exhaust the stack's memory and the program crashes with a stack-overflow error. Iterative solutions or tail-call optimisation (where supported) avoid this.

Q2 Why is naive Fibonacci so slow?

Because it recomputes overlapping subproblems. fib(5) calls fib(4) and fib(3); fib(4) also calls fib(3); and so on, forming a branching tree with ~2ⁿ nodes. Memoisation (or bottom-up dynamic programming) stores each result once, reducing the count to n distinct subproblems.

Q3 Is recursion ever strictly necessary?

No — any recursion can be rewritten as a loop plus an explicit stack, and vice versa. The choice is expressiveness, not power. For self-similar, tree-shaped data (parsers, file systems, divide-and-conquer), recursion is dramatically clearer. For simple linear repetition, iteration is usually simpler and avoids stack limits.

Q4 How do I know my recursion will terminate?

Identify a measure — a non-negative quantity that strictly decreases on every recursive call and triggers a base case at its minimum. For factorial(n) the measure is n, dropping by 1 each time until it hits the base case at 0. If you cannot name such a decreasing measure, you have no termination guarantee.

Concept mind map

How the ideas connect

Every key idea in this chapter, branching from the core concept — use it to see the whole picture at a glance.

base caserecursive casesmaller inputcall stackunwindmemoisationRecursion
Infographic

The process, step by step

Step 1Check base caseIf the input is small enough, return the answer directly.
Step 2ReduceOtherwise shrink the problem toward the base case.
Step 3RecurseCall the function on the smaller input.
Step 4CombineUse the returned result to build this level answer.
Solved examples

Worked problems, step by step

Follow each solution line by line, then try to reproduce it on paper before moving on.

Example 1Compute factorial(4) where n! = n * (n-1)! and 0! = 1.

  1. 4! = 4 * 3!
  2. 3! = 3 * 2!, 2! = 2 * 1!, 1! = 1 * 0!, 0! = 1.
  3. Multiply back up: 1,1,2,6,24.

Example 2Give the 5th Fibonacci number where F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2).

  1. 0, 1, 1, 2, 3, 5...
  2. F(5) is the term after 3.
Practice problem set

Now you try

Work each one out first, then tap to reveal the worked answer.

1What is a base case?
The case solved directly without further calls; it stops the recursion from going forever.
2What is the recursive case?
The part that solves the problem by calling the function on a smaller input and combining the result.
3Why must the input get smaller each call?
A decreasing measure guarantees the recursion reaches the base case and terminates.
4What happens without a base case?
The function keeps calling itself, causing infinite recursion and a stack overflow.
5What is memoisation?
Caching results of subproblems so repeated calls (like in naive Fibonacci) are not recomputed.
6Compute factorial(3).
3! = 3 * 2 * 1 = 6.