Chapter 5: Recursion and Divide-and-Conquer Fundamentals
5.03Beginner6 topics18 min1 interactive animation

Recursion patterns

Recursive methods fall into a few recognisable shapes. Naming the shape tells you the shape of the call tree, how deep the stack gets, and roughly how much work is done.

Learning goals

  • Classify a method as linear, binary or multiple-branching recursion
  • Tell head recursion from tail recursion and say where the work happens
  • Write mutually recursive methods
  • Decide between a void recursion and one that returns a value

Prerequisites

Recursion mechanicsClassical recursion examples
5.03.1

Linear recursion

Linear recursion makes at most one recursive call per invocation. The call tree is a single chain: factorial, sum of digits, reversing an array. For n levels it makes n calls and uses O(n) stack.

Many linear recursions are really loops in disguise, and the iterative version needs no stack.

Java
1static int sum(int[] a, int i) {
2 if (i == a.length) return 0; // base case
3 return a[i] + sum(a, i + 1); // exactly one recursive call
4}
5.03.2

Binary recursion

Binary recursion makes two recursive calls. The call tree is a binary tree, and the number of calls depends on how the problem shrinks: Fibonacci shrinks by 1 and 2 (exponential calls), merge sort halves (O(n) calls), and Tower of Hanoi shrinks by 1 twice (2^n - 1 moves).

In the animation each disk move is one visit of the middle step: move n - 1 disks away, move the biggest, move n - 1 disks back on top. The call count doubles with each extra disk, which is why 64 disks would take about 1.8 × 10^19 moves.

DisksMoves 2^n - 1At one move per second
53131 seconds
101,02317 minutes
201,048,57512 days
6418,446,744,073,709,551,615585 billion years

Worst

O(2^n)

Space

O(n) stack

Tower of Hanoi

Interactive · edit the input

Your input

Try

Three pegs · 3 disks

3
2
1

A

B

C

Peg A: 3, 2, 1. Peg B: empty. Peg C: empty

Setup

Move all 3 disks from A to C. Only one disk moves at a time and a disk may never sit on a smaller one. The minimum is 2^3 - 1 = 7 moves.

Step 1 / 16
Java
1static void hanoi(int n, char from, char to, char via) {
2 if (n == 0) return;
3 hanoi(n - 1, from, via, to); // park the n-1 smaller disks on 'via'
4 System.out.println("Move disk " + n + ": " + from + " -> " + to);
5 hanoi(n - 1, via, to, from); // bring them onto the big disk
6}

Live state

0 moves0 calls

n

3

No condition evaluated on this step

Call stack (top first)

  1. empty
Execution trace (dry run table)Show 16 steps
StepLineStateWhat happens
1L1n=3Move all 3 disks from A to C. Only one disk moves at a time and a disk may never sit on a smaller one. The minimum is 2^3 - 1 = 7 moves.
2L1n=3 from=A to=C via=BTo move 3 disks from A to C: first park the 2 above the biggest on B.
3L1n=2 from=A to=B via=CTo move 2 disks from A to B: first park the 1 above the biggest on C.
4L1n=1 from=A to=C via=BMove 1 disk from A to C: nothing sits above it, so it can move directly.
5L4n=1 disk=1 from=A to=CMove disk 1 from A to C (move 1).
6L4n=2 disk=2 from=A to=BMove disk 2 from A to B (move 2).
7L1n=1 from=C to=B via=AMove 1 disk from C to B: nothing sits above it, so it can move directly.
8L4n=1 disk=1 from=C to=BMove disk 1 from C to B (move 3).
9L4n=3 disk=3 from=A to=CMove disk 3 from A to C (move 4).
10L1n=2 from=B to=C via=ATo move 2 disks from B to C: first park the 1 above the biggest on A.
11L1n=1 from=B to=A via=CMove 1 disk from B to A: nothing sits above it, so it can move directly.
12L4n=1 disk=1 from=B to=AMove disk 1 from B to A (move 5).
13L4n=2 disk=2 from=B to=CMove disk 2 from B to C (move 6).
14L1n=1 from=A to=C via=BMove 1 disk from A to C: nothing sits above it, so it can move directly.
15L4n=1 disk=1 from=A to=CMove disk 1 from A to C (move 7).
16L6moves=7Done in 7 moves = 2^3 - 1. The count doubles (plus one) with every extra disk: T(n) = 2·T(n-1) + 1.
5.03.3

Multiple branching

Multiple branching makes several recursive calls in a loop: generating permutations, subsets, or all paths through a grid. The tree has many children per node, and its size is bounded by the branching factor raised to the depth.

This is the structure of backtracking (Chapter 20). The recursion tree for permutations of n items has n! leaves.

Java
1static void permutations(int[] a, int k, List<int[]> out) {
2 if (k == a.length) { out.add(a.clone()); return; } // one complete arrangement
3 for (int i = k; i < a.length; i++) { // up to n - k branches
4 swap(a, k, i);
5 permutations(a, k + 1, out);
6 swap(a, k, i); // undo, so the next branch starts clean
7 }
8}
5.03.4

Head vs tail recursion

In head recursion the recursive call comes first and the work is done afterwards, on the return path. In tail recursion the work comes first and the recursive call is the last action. The two print in opposite orders.

Tail recursion can be turned into a loop mechanically, which is what some compilers do. Head recursion usually needs a stack, explicit or implicit, because work is still pending when the call is made.

Java
1static void countDown(int n) { // work before the call
2 if (n == 0) return;
3 System.out.print(n + " ");
4 countDown(n - 1); // prints 3 2 1
5}
6
7static void countUp(int n) { // work after the call
8 if (n == 0) return;
9 countUp(n - 1);
10 System.out.print(n + " "); // prints 1 2 3
11}
5.03.5

Mutual recursion

In mutual recursion two or more methods call each other. Each call moves toward the base case, but through the other method. Typical uses are parsers (an expression contains a term, which contains an expression in parentheses) and state machines.

The same termination rules apply: every cycle of calls must shrink the input.

Java
1static boolean isEven(int n) { return n == 0 ? true : isOdd(n - 1); }
2static boolean isOdd(int n) { return n == 0 ? false : isEven(n - 1); }
3// isEven(3) -> isOdd(2) -> isEven(1) -> isOdd(0) -> false
5.03.6

Recursion with return value

A recursion can return a value that is combined on the way back (sum, max, count), or return nothing and act through side effects (printing, filling a list, swapping array elements).

Return values keep the state local to each call and are easier to reason about. Side effects need careful handling of shared state: if a recursive call changes a list or array, every later call sees the change, so undo it (backtracking) or copy it.

StyleExampleWatch for
Returns a valuemax of an array, tree heightCombining the children's results correctly
Void with side effectsprint a tree, fill a result listShared mutable state, undoing changes

Practice ladder

Now try it yourself

Easy

  • Print an array backwards recursively
  • Count the nodes of a linked list recursively
  • isEven and isOdd with mutual recursion

Medium

  • Generate all binary strings of length n
  • Print all subsets of a set
  • Move n disks in Hanoi and count the moves

Advanced

  • Generate all permutations without duplicates when the input has repeats
  • Evaluate a nested arithmetic expression with mutually recursive parse methods
  • Prove Hanoi needs at least 2^n - 1 moves

Key takeaways

  • Linear recursion makes a chain, binary recursion a binary tree, multiple branching a wide tree.
  • Head recursion does its work on the way back; tail recursion does it on the way down.
  • Prefer returning values over shared mutable state, or undo your changes after each branch.