Recursion and the stack: when a function calls itself
Recursion solves nested problems in a few lines. Learn base cases, tree walks, stack depth limits, tail calls, and how to rewrite recursion with your own stack.
Comes up in: DSA, Coding
Learn#
Meera’s comment section has replies to replies to replies. She needs to count every comment, but she cannot know how deep the nesting goes. A for loop needs to know the depth. A function that calls itself does not.
That is recursion: a function solves a big problem by calling itself on a smaller piece. It needs a base case, a case small enough to answer directly. Without one, it never stops.
Trees are where recursion shines: menus, folders, the DOM, JSON, comment threads. Each part of a tree is a smaller tree.
function count(list) {
let total = 0;
for (const item of list) total += Array.isArray(item) ? count(item) : 1;
return total; // a list with no inner lists is the base case
}
console.log(count([1, [2, [3]]]));Output (verified)
3
Each call waits on the call stack (JS.01.01) until the call inside it returns.
Call stack
- count([1,[2,[3]]])
Console
empty
We call count([1, [2, [3]]]). One frame goes on the call stack.
Read the steps as text
- We call count([1, [2, [3]]]). One frame goes on the call stack.
- It finds the inner array [2, [3]] and calls itself. A second frame goes on top.
- Again for [3]. Three frames now wait on the stack.
- [3] holds no arrays: the base case. It returns 1 and its frame is removed.
- Each waiting frame adds its own numbers and returns. The stack empties and the answer is 3.
Predict#
Predict first
In Node.js, a correct recursive function walks a list nested 100,000 levels deep. What happens?
Each level needs a stack frame, and the stack has a fixed size. In Node.js 25, the count function above fails at about 5,000 levels. The exact number depends on the engine and on how big each frame is. Tail-call optimization would not help: V8 does not implement it.
Interview angle#
Say it like this — about 30 seconds
“Recursion is a function calling itself on a smaller input until a base case returns directly. It fits trees, such as the DOM, nested JSON or folder structures. Every call takes a stack frame, so very deep input throws a RangeError. The spec has proper tail calls, but only Safari ships them; V8 and Firefox do not. So for deep or untrusted input, I rewrite the recursion as a loop with my own stack.”
Follow-up Does JavaScript have tail-call optimization?
Follow-up How do you avoid a stack overflow on deep input?
Follow-up Recursion or a loop: which do you pick?
Practice#
Guided
Write flatten with recursion, then flattenIterative with a loop and your own stack. Do not use Array.prototype.flat.
Done when
- Both return every value in the original order, at any depth.
- Empty arrays inside the input disappear from the result.
flattenIterativehandles an array nested 100,000 levels deep.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What does every recursive function need so that it stops?
A base case: an input small enough to answer without calling itself.
What error do you get when recursion goes too deep?
RangeError: Maximum call stack size exceeded.
Which major JavaScript engine implements proper tail calls?
Only Safari's JavaScriptCore. V8 and SpiderMonkey do not.
How do you turn recursion into iteration?
Keep pending work in an array used as a stack, and loop until it is empty.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
Write the base case first, then the step that makes the input smaller. Check that every call gets closer to the base case: a smaller array, a shorter string, a child node.
Check yourself: What happens if a recursive function has no base case?
It calls itself forever until the stack overflows with a RangeError.
How it works underneathSenior rounds
Each call pushes a frame holding its arguments and local variables. The frame is removed when the call returns. Depth, not total work, uses up the stack: a balanced tree of a million nodes is about 20 levels deep, so it is safe.
Check yourself: Where are a recursive call's local variables stored?
In its own stack frame, separate from the other calls.
Edge cases and trapsWhere people slip
count after about 5,000.return f(n - 1). ES2015 says engines must reuse the frame for tail calls in strict code. Today only Safari ships this (V8 and SpiderMonkey do not implement proper tail calls) Source: ECMA-262: Tail position calls.Check yourself: Is return f(n - 1) + 1 a tail call?
No. The addition runs after f returns, so the frame must stay.
In productionStaff-level answers
Check yourself: Why use an explicit stack for user-supplied JSON?
Users control the depth, so deep input could crash recursive code.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- Does JavaScript have tail-call optimisation?