Recursion: how a function calls itself
When one function calls another, the first stops where it is, the second runs, and the first carries on from exactly where it paused. Recursion is that, with one change: the function it calls is itself.
Which raises two questions at once. If it keeps calling itself, what makes it ever stop? And if four copies are running at the same time, each with a different n, how does any of them remember which n was theirs? Both answers are below. Press play.
1function factorial(n) {2 if (n <= 1) return 13 const smaller = factorial(n - 1)4 return n * smaller5}
Call tree
every call, and who called it
Call stack
what each one is holding
- factorial(4)line 1n = 4
1 frame
We start with factorial(4). Nothing has been worked out yet. It is a question waiting for an answer.
What is happening, why it stops, and how each call keeps its own values.
What is actually happening
A call that is too big to answer outright hands a smaller version of the same question to a fresh copy of itself, and then waits. That copy does the same. Sooner or later a copy gets a question small enough to answer with no help at all. That is the base case, and it hands its answer straight back. Every waiting call then wakes in turn, finishes its own bit of work, and passes its answer up.
So nothing is worked out on the way down. The descent only breaks the problem into smaller pieces. The answer is built on the way back up.
Why it stops
Every recursive function has two branches, and it needs both. The recursive case is the one that calls itself, always on a smaller input than it was given. The base case is the one that answers directly, with no call at all.
The input shrinking is what guarantees you reach the base case. Remove the base case and nothing ever stops the descent; stop shrinking the input and you never arrive at it. Either mistake produces the same symptom, which is why they are worth separating in your head.
How each call remembers its own n
When a call is interrupted it needs somewhere to keep the values it was working with, so they are still there when it resumes. That somewhere is a stack frame: a small block holding this call's n, and the point in the code to carry on from.
Frames are kept on the call stack, which is a stack in the ordinary sense: added to and removed from one end only. Look at the frames beside the animation while it runs. Four calls in flight means four frames, each holding a different n at the same moment. They are not sharing one variable and taking turns. Each call got its own.
That is the whole answer to “how does it remember?”. It does not have to remember. Nothing ever overwrote it.
Where the answer goes
When a call finishes it hands back a return value, and that value goes to exactly one place: the call that was waiting for it, which resumes on the very line where it paused. Nothing is broadcast and nothing jumps back to the start.
Watch the highlighted line as you step. With factorial, line 3 makes the call and line 4 does the multiplication. Line 4 cannot run until line 3 has produced a value, and that is the whole reason the work happens on the way back up.
Now switch to countdown and the order flips. It prints before it calls itself, so all of its work happens on the way down and the return journey does nothing at all. Same shape, opposite timing.
What a frame costs
Every frame is real memory. For a function like factorial that is typically tens of bytes: the saved return address, the saved frame pointer, and the locals. Call depth is therefore a memory cost that grows in step with n.
The space those frames come from is not unlimited either. It was set aside before the program started and it does not grow, so depth is not only a cost but a ceiling. What that ceiling is, and what happens when a program reaches it, is the whole of the next lesson.
Why fib is a different animal
countdown and factorial make one call each, so their call trees are a straight line: n calls, depth n. fib makes two, so its tree branches, and the number of calls stops growing with n and starts growing like 1.618n, the golden ratio.
Set the animation to fib with n = 5 and count: fifteen calls to produce the answer 5, with fib(1) worked out five separate times from scratch. Nothing is remembered between branches.
The depth is still only n, though, because the tree is walked one path at a time. Wide in total work, narrow at any one moment, which is exactly what the frames show while the tree fans out behind them.
Fixing that repeated work, by keeping answers instead of recomputing them, is a later lesson in this course. It is the same tree with most of it deleted.
Recursion against a loop
Anything recursion can do, a loop can do. The two are equivalent in power, so the real trade is which one makes the code match the problem.
For factorial, a loop is honestly better: same answer, no frames, no depth limit. For walking a tree, recursion wins clearly, because a tree is defined in terms of smaller trees and the code can say exactly that. Write the loop version of a tree walk and you will find yourself building a stack by hand. The same stack, only now it is one you have to maintain yourself.
Tail calls, and why they do not save you here
When a recursive call is the last thing a function does, there is nothing left to come back for, so the frame could be reused instead of stacked. Some languages guarantee this: Scheme requires it, and C compilers often do it with optimisation turned on.
JavaScript engines, in practice, do not, so the stack you see in the animation is the stack you get. It is also worth noticing that factorial as written here is not tail recursive anyway: the multiplication on line 4 happens after the call returns, so the frame still has work to do and cannot be discarded.
Three questions
Pick an answer before you open one. Being wrong here is the useful part, and it is the whole reason to answer rather than read.
factorial(4) has counted its way down to factorial(1). What does factorial(1) do?
factorial(2) works out its answer and finishes. Where does that answer go?
factorial(4) takes 4 calls in total. How many does fib(5) take?
Then write some
Work these out on paper before running them. The point is to predict, and the useful moment is the one where your prediction is wrong.
Sum the numbers from 1 to n
sum(4) should give 10. Fill in the two branches.
function sum(n) {
if (???) return ???
return ??? + sum(??? )
}Show the answer →Hide the answer
function sum(n) {
if (n === 0) return 0 // base case: nothing left to add
return n + sum(n - 1) // recursive case: n, plus the rest
}The same shape as factorial, with + instead of × and 0 instead of 1 as the base. Zero is the value that changes nothing when you add, the way one changes nothing when you multiply.
Count the digits in a number
digits(4051) should give 4. What is the base case, and what makes the input smaller?Show the answer →Hide the answer
function digits(n) {
if (n < 10) return 1
return 1 + digits(Math.floor(n / 10))
}Dividing by ten is what shrinks the input. Notice the base case is not zero here. A single digit is already answerable, and starting at n === 0 would make digits(0) return 0 instead of 1.
Reverse a string
reverse('abcd') should give 'dcba'. Make the input smaller by one character each time.Show the answer →Hide the answer
function reverse(s) {
if (s.length <= 1) return s
return reverse(s.slice(1)) + s[0]
}The first character has to end up last, so it is added after the recursive call returns. Work on the way back up, exactly like factorial.
Spot the bug
This runs forever. Why?
function countdown(n) {
if (n === 0) return
console.log(n)
countdown(n)
}Show the answer →Hide the answer
countdown(n - 1) // the input has to shrinkThere is a base case, and it is correct. But the input never gets closer to it, so it is never reached. A base case alone is not enough. Every recursive call has to move toward it.
Predict the order
What does this print for n = 3, and why is it not 3 2 1?
function f(n) {
if (n === 0) return
f(n - 1)
console.log(n)
}Show the answer →Hide the answer
1 2 3The print happens after the recursive call, so nothing prints until the descent finishes. Move the line above the call and you get 3 2 1. That single line decides whether work happens on the way down or on the way back up. Set the animation to countdown to watch the other order.
Flatten a nested list
flatten([1, [2, [3, 4]], 5]) should give [1, 2, 3, 4, 5]. The nesting can be any depth.Show the answer →Hide the answer
function flatten(list) {
const out = []
for (const item of list) {
if (Array.isArray(item)) out.push(...flatten(item))
else out.push(item)
}
return out
}The first problem here where a loop could not replace the recursion without you building a stack yourself, because you do not know the depth in advance. This is the case recursion is actually for.
What gets asked, and what the asker is checking. Answer out loud before opening each one. The gap between knowing something and being able to say it out loud is the thing interviews actually measure.
+What are the two parts every recursive function needs?
A base case that returns without calling itself, and a recursive case that calls itself on a smaller input. Both, or it does not terminate.
The stronger answer adds the reason: the base case is where the descent ends, and the shrinking input is what guarantees you reach it. Candidates who name only the base case usually cannot spot the infinite loop in problem 4 above.
+Is recursion slower than iteration?
Usually a little, yes. Each call costs a frame to set up and tear down, and a loop does not, but the difference is a constant factor rather than a change in complexity.
The trap in this question is naive fib. It is slow because it answers the same small questions over and over, not because it recurses. Memoise it and it is linear, still recursive. Conflating the two is the mistake the question is looking for.
+When would you choose recursion over a loop?
When the data is defined recursively, as trees and nested structures and divide-and-conquer all are, the recursive code says what the problem is and the iterative version usually ends up maintaining a stack by hand. When the problem is a flat sequence, a loop is simpler and has no depth limit.
+What is tail recursion?
A call that is the last thing the function does, with no work left afterwards. Because there is nothing to return to, the frame can be reused rather than stacked, giving constant stack space. Scheme guarantees it and C compilers commonly do it; JavaScript engines in practice do not. Note that factorial as usually written is not tail recursive, because the multiplication happens after the call comes back.
+Walk me through what happens in memory when factorial(3) runs.
A frame for factorial(3) is pushed, holding n = 3, and it pauses at the recursive call. The same for factorial(2), then factorial(1), which hits the base case and returns 1 without calling anything. Its frame pops. factorial(2) resumes on the line it paused on, computes 2 × 1 = 2, returns, and pops. factorial(3) resumes, computes 3 × 2 = 6, returns 6. Three frames at peak, all gone at the end. Scrub the animation while you say it.
Know someone stuck on this? Send it to them.
A classmate, a study group, someone learning this on their own at midnight. The link opens the lesson set up exactly as you have it, the same function and the same number, and runs from the first step so they watch the whole thing build rather than landing in the middle of it.
Next in this course
The call stack, and what “stack overflow” means
The same frames you just watched, but with a ceiling, and what happens when a program reaches it.
Start there →