Chapter 5: Recursion and Divide-and-Conquer Fundamentals
5.01Beginner8 topics20 min1 interactive animation

Recursion mechanics

A recursive method solves a problem by calling itself on a smaller version of the same problem. Every recursive method needs two things: a base case that stops the recursion, and a recursive case that makes progress toward it.

Learning goals

  • Identify the base case and the recursive case of a method
  • Trace the call stack as calls are made and as they return
  • Explain why a recursion terminates and state an invariant
  • Recognise tail recursion and know what the JVM does with it

Prerequisites

Java methodsStack data structure (intuition)Loops

Looking up a word you do not know in a dictionary, whose definition uses another unknown word, means looking that word up first. You keep going until you reach a word you know (the base case), then work back out with each answer. That is recursion.

5.01.1

Base case

The base case is the input for which the answer is known directly, with no further recursive call. Without it the method calls itself forever.

Choose the smallest input that is still meaningful: n == 0 for factorial, an empty or one-element range for array problems, null for linked structures. A recursion can have several base cases, such as fib(0) and fib(1).

Java
1static long factorial(int n) {
2 if (n == 0) return 1; // base case: 0! is 1 by definition
3 return n * factorial(n - 1); // recursive case
4}
5.01.2

Recursive case

The recursive case reduces the problem and trusts the recursive call to solve the smaller version. You do not need to follow every call in your head: assume factorial(n - 1) works, and then check that n * factorial(n - 1) is correct for n.

That trust is justified by induction: if the base case is correct and each call is correct whenever its smaller calls are, the whole method is correct.

5.01.3

Call stack

Each call gets its own frame on the call stack, holding its parameters, local variables and the place to return to. A new call pushes a frame, and a return pops it.

The animation shows factorial(4). Watch the stack grow as each call waits for the next, and shrink as the answers come back. The recursion tree on top is the same story drawn as a picture: each box is one call, and its value appears when it returns.

Worst

O(n) time

Space

O(n) stack

Factorial: calls, base case and return path

Interactive · edit the input

Your input

Try

Recursion tree · 1 call

factorial(4)…
Running now
Call

Call factorial(4). It is frame number 1 on the call stack.

Step 1 / 20
Java
1static long factorial(int n) {
2 if (n == 0) return 1; // base case
3 return n * factorial(n - 1); // recursive case
4}

Live state

1 calls

n

4

depth

1

No condition evaluated on this step

Call stack (top first)

  1. factorial(4)
Execution trace (dry run table)Show 20 steps
StepLineStateWhat happens
1L1n=4 depth=1Call factorial(4). It is frame number 1 on the call stack.
2L2n=4n is not 0, so this call cannot answer yet. It needs factorial(3).
3L3n=4Pause factorial(4) and call factorial(3). The pending multiplication by 4 waits on the stack.
4L1n=3 depth=2Call factorial(3). It is frame number 2 on the call stack.
5L2n=3n is not 0, so this call cannot answer yet. It needs factorial(2).
6L3n=3Pause factorial(3) and call factorial(2). The pending multiplication by 3 waits on the stack.
7L1n=2 depth=3Call factorial(2). It is frame number 3 on the call stack.
8L2n=2n is not 0, so this call cannot answer yet. It needs factorial(1).
9L3n=2Pause factorial(2) and call factorial(1). The pending multiplication by 2 waits on the stack.
10L1n=1 depth=4Call factorial(1). It is frame number 4 on the call stack.
11L2n=1n is not 0, so this call cannot answer yet. It needs factorial(0).
12L3n=1Pause factorial(1) and call factorial(0). The pending multiplication by 1 waits on the stack.
13L1n=0 depth=5Call factorial(0). It is frame number 5 on the call stack.
14L2n=0Base case reached: no more recursion. Everything below this point is the return path.
15L2n=0 return=1return 1.
16L3n=1 return=1factorial(1) resumes: 1 × 1 = 1. Return it to the caller.
17L3n=2 return=2factorial(2) resumes: 2 × 1 = 2. Return it to the caller.
18L3n=3 return=6factorial(3) resumes: 3 × 2 = 6. Return it to the caller.
19L3n=4 return=24factorial(4) resumes: 4 × 6 = 24. Return it to the caller.
20L3return=24factorial(4) = 24, using 5 calls and a maximum stack depth of 5.
5.01.4

Return path

Work can happen on the way down (before the recursive call), on the way back up (after it returns), or both. In n * factorial(n - 1) the multiplication happens on the return path: no multiplication is done until the base case has returned 1.

Step through the animation after the base case. Each frame resumes, multiplies by its own n, and returns to its caller. That return path is where the answer is actually assembled.

CallWaiting forReturns
factorial(3)factorial(2)3 × 2 = 6
factorial(2)factorial(1)2 × 1 = 2
factorial(1)factorial(0)1 × 1 = 1
factorial(0)nothing (base case)1
5.01.5

Stack frames

A stack frame stores the method's parameters, local variables, and the return address. Because each frame has its own copy of the variables, recursive calls do not overwrite each other: the n in factorial(3) and the n in factorial(2) are different variables.

Frames take memory, typically tens to a few hundred bytes each. The JVM thread stack is limited (often 512 KB to 1 MB by default, settable with -Xss), which is why very deep recursion fails. Objects live on the heap, not in the frame; the frame holds only references to them.

5.01.6

Termination

A recursion terminates if every call moves strictly closer to a base case. Name the quantity that shrinks: n decreases by 1, the range hi - lo halves, the list gets shorter. If you cannot name one, the recursion may loop forever.

Check two things: the base case is reachable from every valid input, and the recursive call makes progress. factorial(n) with a negative n never reaches n == 0 because it keeps subtracting. Guard against it or use n <= 0 for the base case with an input check.

5.01.7

Recursive invariants

An invariant is a fact that holds at the start of every call. Stating it is the best way to prove a recursion correct. For recursive binary search the invariant is: "if the target is in the array, it is in arr[lo..hi]". Each call keeps it true while shrinking the range.

For reverse(a, lo, hi): "everything outside lo..hi is already in its final place". Each call fixes two more elements and the invariant still holds for the smaller range.

5.01.8

Tail recursion

A call is a tail call when it is the very last thing the method does and its result is returned unchanged. gcd(b, a % b) is a tail call. n * factorial(n - 1) is not, because the multiplication still has to happen afterwards.

Some languages reuse the current frame for a tail call, so the recursion uses no extra stack. The JVM does not do this. A tail-recursive Java method still uses one frame per call, so convert it to a loop when the depth could be large (see lesson 5.07).

Java
1// not a tail call: the multiplication waits for the result
2static long fact(int n) { return n == 0 ? 1 : n * fact(n - 1); }
3
4// tail call with an accumulator: nothing is left to do after the call
5static long factTail(int n, long acc) { return n == 0 ? acc : factTail(n - 1, acc * n); }

Practice ladder

Now try it yourself

Easy

  • Print the numbers from n down to 1 recursively
  • Compute the sum of 1..n recursively
  • Count the digits of an integer recursively

Medium

  • Check whether a string is a palindrome recursively
  • Compute x^n with recursion
  • Draw the call stack for factorial(5) on paper

Advanced

  • Prove by induction that your recursive method is correct
  • Find the deepest n your JVM handles for sum(n) and explain the limit
  • Write factTail and rewrite it as a loop

Key takeaways

  • A recursive method needs a reachable base case and a recursive case that makes progress.
  • Every call has its own stack frame; answers are assembled on the return path.
  • State an invariant and a shrinking quantity to argue correctness and termination; Java does not optimise tail calls.