Call factorial(5) - a new frame is pushed onto the call stack.
primitives are shown in Variables.
Real execution, step by step. Press play or scrub with the controls - every variable and array index updates live.
Start here: what this lesson is for
Recursion is the topic where people most often decide they are 'not a natural programmer'. That reaction is almost always caused by being taught it backwards - shown a clever example and asked to trace it in their head.
You are not going to trace anything in your head here. The whole approach in this lesson is to trust the smaller call and check only two things, and the animation above does the tracing for you so you can watch what your head cannot hold.
By the end you will be able to write a recursive method by answering two questions, read a stack of frames without losing your place, and know why deep recursion crashes rather than merely slowing down.
This lesson matters more than its length suggests. Trees, graphs, backtracking and dynamic programming - four later chapters - are all recursion in different clothes. Time spent here pays back four times.
- What you need first: lessons 1 to 3, and comfort with a method calling another method.
- What you will be able to do: write and read recursive code without tracing it mentally.
- How long: about 22 minutes, plus two problems at the end.
- recursion
- - a method that solves a problem by calling itself on a smaller version of it
A function that calls itself
Every function you've written so far calls other functions. Recursion is the moment a function calls itself - and the first time you see it, it looks like either a typo or black magic. It's neither. It's the most natural way in the world to solve any problem that contains a smaller copy of itself.
Take factorial: 5! means 5 x 4 x 3 x 2 x 1. Look closely and there's a smaller factorial hiding inside - 5! is just 5 x 4!. And 4! is 4 x 3!. Each version of the problem contains a slightly smaller version, all the way down to 1!, which we simply know: it's 1. No further work needed.
That's the entire recipe, and every recursive function you will ever write has the same two parts: a base case - the input so small you can answer it on the spot - and a recursive case - answer the big problem using the answer to the smaller one. Press play above and watch factorial(5) do exactly this.
Imagine it like this: Russian nesting dolls. To count how many dolls there are, open one and ask 'how many are inside THIS one?' - then add 1. The tiniest doll doesn't open: that's the base case, and the counting unwinds from there.
- recursion
- - a function solving a problem by calling itself on a smaller version of it
- base case
- - the input small enough to answer directly - where the calls stop
- recursive case
- - the step that reduces the problem and combines the smaller answer
Watch the tower rise and fall
Now turn your eyes to the call stack panel in the animation (toggle it on in Panels if it's off). Every time factorial calls itself, a new frame appears - a box holding that call's own private n. factorial(5) sits at the bottom, then factorial(4) stacks on top, then 3, 2, and finally factorial(1).
At factorial(1), the base case fires: it returns 1 without recursing. And now the beautiful part - the tower unwinds. factorial(2) had been waiting for that answer; it multiplies 2 x 1 and returns 2. factorial(3) wakes up, computes 3 x 2 = 6. Then 4 x 6 = 24, then 5 x 24 = 120. Each frame pops as it hands its answer down.
This is the mental model that beats all the confusion: a recursive call doesn't 'jump back into the function' - it creates a brand new copy of it, with its own variables, stacked on top of the caller, which simply pauses and waits.
There isn't one n changing value - there are five separate n's alive at once, one per frame: 5, 4, 3, 2, 1. Watch the Variables panel as you scrub: each frame remembers its own.
- Calling itself creates a NEW frame with its own variables
- The caller pauses and waits for the callee's return value
- The base case is where the tower stops growing
- Answers combine on the way back DOWN the stack
- call stack
- - the pile of active function calls; the top one is running, the rest wait
- frame
- - one call's private world - its parameters and local variables
What happens if you forget to stop
Delete the base case from factorial and run it in your head: factorial(5) calls factorial(4) calls factorial(3)... past zero, into the negatives, forever. Except not forever - each call costs a frame of memory, and the stack has a limit. When the tower hits it, the program dies with the most famous crash in programming: StackOverflowError (Java) - the error an entire website is named after.
So whenever a recursive function misbehaves, check the same two suspects in order: is there a base case, and does every recursive call actually move toward it? A call that doesn't shrink the problem is a call that never ends.
Imagine it like this: An out-of-office reply that says 'for details, email me'. Your auto-reply triggers theirs, theirs triggers yours - nothing shrinks, nothing stops, until a server gives up. That server's limit is your stack size.
Before running any recursion, ask: 1) What input stops it? 2) Does each call get closer to that input? If either answer is fuzzy, you've found tomorrow's stack overflow.
- stack overflow
- - the crash when recursion piles up more frames than the stack can hold
Recursion vs loops - the honest comparison
Could you write factorial with a loop? Absolutely - and for factorial, the loop is arguably simpler. So why learn recursion at all? Because loops flatten; recursion branches. A loop walks a straight line. Recursion can explore a tree of possibilities - and trees, as you'll see from Chapter 8 onward, are everywhere: file systems, decision trees, search spaces, the DOM of every web page.
The cost side: factorial(n) makes n calls doing constant work each - O(n) time, same as the loop. But it also holds up to n frames alive at once, so it spends O(n) memory on the stack, where the loop spends O(1). That trade - elegance and tree-power for stack space - is the recurring theme of this chapter, and the next lesson makes the cost side dramatic.
- Anything a loop can do, recursion can do - and vice versa
- Recursion wins when the problem branches like a tree
- factorial(n): O(n) time like the loop, but O(n) stack memory too
- Deep recursion risks stack overflow; loops never do
The two questions that write the method for you
Here is the practical technique, and it deliberately never asks you to picture the whole recursion.
Question one: when is it trivially over? That is the base case. For counting down, it is reaching zero. For a list, it is the empty list. For a tree, it is a null node. Write this first, always - a recursion without a base case is an infinite loop with extra steps.
Question two: assuming the smaller call already works, how do I use its answer? This is the leap people resist, because it feels like assuming the thing you are trying to prove. It is not. It is the same trust you place in any method you did not write: you call Math.max without re-deriving it, and you call factorial(n-1) the same way.
That is the whole method. Note what is missing: at no point did you trace five levels of calls in your head. If you find yourself doing that, you have skipped question two and you will get tired long before you get correct.
// Q1: when is it trivially over? -> an empty array has sum 0
// Q2: if sumFrom(i+1) already works, how do I use it?
// -> add a[i] to whatever it returns
int sumFrom(int[] a, int i) {
if (i == a.length) return 0; // Q1: base case
return a[i] + sumFrom(a, i + 1); // Q2: trust the smaller call
}
// the same two questions, on a string
boolean isPalindrome(String s, int lo, int hi) {
if (lo >= hi) return true; // Q1: 0 or 1 chars left
if (s.charAt(lo) != s.charAt(hi)) return false;
return isPalindrome(s, lo + 1, hi - 1); // Q2: trust the middle
}sumFrom([3,1,4], 0)
3 + sumFrom([3,1,4], 1)
1 + sumFrom([3,1,4], 2)
4 + sumFrom([3,1,4], 3)
0 <- base case
= 4
= 5
= 8
isPalindrome("racecar", 0, 6) -> trueRecap, and where this goes next
You now have three things: a way to write recursion (the two questions), a way to read it (the stack of frames, newest on top), and an understanding of why it can crash rather than merely slow down.
The stack overflow is worth holding onto. Every call keeps its own frame alive until it returns, so a recursion a hundred thousand deep needs a hundred thousand live frames - and the call stack is small and fixed. That is a hard limit, not a performance problem, and chapter 7 shows how to escape it by carrying your own stack on the heap.
Next lesson turns recursion into backtracking: the same two questions, plus the discipline of undoing a choice when it fails. After that, recursion mostly disappears as a topic and shows up as the natural way to write everything else - which is the point.
- Write the base case first; a missing one is an infinite loop
- Trust the smaller call - do not trace it mentally
- Each live call holds a frame; deep recursion overflows the stack
- The answers assemble on the way BACK OUT, not on the way in
- Trees, graphs, backtracking and DP are all this, reused
Your turn
Reading is not learning. Open each one in NeonFlow and watch your own code run, step by step.
Answer the two questions
Write a recursive method that reverses a string. Do not plan the whole thing - answer the two questions only. When is it trivially over? and if the smaller call works, how do I use it? Then watch the frames stack and unwind in NeonFlow.
public class ReverseIt {
static String reverse(String s) {
// Q1: what string is ALREADY reversed, with no work?
// TODO: base case
// Q2: if reverse() works on a shorter string, how do you build
// the answer for this one?
// TODO: recursive case
return s;
}
public static void main(String[] args) {
System.out.println(reverse("flame")); // expect "emalf"
System.out.println(reverse("a")); // expect "a"
System.out.println(reverse("")); // expect ""
}
}Break it on purpose
Delete the base case and run it. You will get a StackOverflowError - the point is to see it happen and read the depth, rather than be told about it. Then put the base case back and watch the same code finish instantly.
public class BreakTheStack {
static int depth = 0;
static int countdown(int n) {
depth++;
// TODO: the base case is missing. Run it, read the crash,
// then add: if (n <= 0) return 0;
return countdown(n - 1);
}
public static void main(String[] args) {
try {
countdown(10);
} catch (StackOverflowError e) {
System.out.println("crashed at depth " + depth);
}
}
}