Algorithms from zero
The call stack
When a function calls itself (recursion), something has to remember what was going on before that call—the parameters, the local variables, the line of code to return to. Your computer keeps a call stack for exactly this: a stack of “stack frames,” one per function call. When a recursive function calls itself, a new frame is pushed. When it returns, the frame is popped. If recursion goes too deep, the stack overflows and the program crashes.
After this lesson you understand how the call stack works: what a stack frame is and what it holds, how the stack grows when functions call each other (and shrinks when they return), why recursive calls build a stack of frames, how the depth of recursion translates directly to the space complexity O(n), and what stack overflow is and how to avoid it.
What is the call stack?
The call stack is a data structure (a stack—LIFO) that keeps track of active function calls. Every time a function is called, a new stack frame is created and pushed onto the stack. The frame holds:
- The function’s parameters.
- Its local variables.
- The return address (where in the calling code to go when this function finishes).
When the function returns, its frame is popped off the stack, and control goes back to the caller.
How the stack grows and shrinks
Consider this simple example:
function foo() { bar(); }
function bar() { baz(); }
function baz() { return 42; }
foo();When you call foo():
- Frame for
foo()is pushed. foo()callsbar(), so a frame forbar()is pushed.bar()callsbaz(), so a frame forbaz()is pushed.baz()returns 42 → its frame is popped.bar()returns → its frame is popped.foo()returns → its frame is popped.
At step 3, the stack contains [foo, bar, baz] (baz on top, the next to run). The stack depth is 3.
Recursion and the call stack
Recursion is just a special case: a function calls itself. Each recursive call pushes a new frame.
Let us trace factorial(4):
- Call
factorial(4)→ push frame[n=4]. - Call
factorial(3)→ push frame[n=3]. Stack is now[factorial(4), factorial(3)]. - Call
factorial(2)→ push frame[n=2]. Stack is now[factorial(4), factorial(3), factorial(2)]. - Call
factorial(1)→ push frame[n=1]. Stack is now[factorial(4), factorial(3), factorial(2), factorial(1)]. factorial(1)hits base case, returns 1 → pop[n=1]. Stack is now[factorial(4), factorial(3), factorial(2)].factorial(2)gets the answer 1, computes 2 × 1 = 2, returns → pop[n=2]. Stack is now[factorial(4), factorial(3)].factorial(3)gets the answer 2, computes 3 × 2 = 6, returns → pop[n=3]. Stack is now[factorial(4)].factorial(4)gets the answer 6, computes 4 × 6 = 24, returns → pop[n=4]. Stack is empty.
At step 4 (the deepest point), the stack depth is 4. This is n—the size of the input.
The stack has a finite size
Your computer’s memory is limited. The stack (the region of memory where frames live) has a fixed size—typically several megabytes. If recursion goes too deep and keeps pushing frames without popping, it can exhaust this space.
When the stack runs out of memory, a stack overflow occurs: the program crashes with an error like “Maximum call stack size exceeded.” This is a hard limit you cannot ignore.
function badLoop(n) {
return badLoop(n + 1); // No base case! Infinite recursion.
}
badLoop(0); // Eventually: stack overflow error.Even with a base case, if the recursion is just deep enough, you will hit the limit:
function deepRecursion(n) {
if (n === 0) return 1;
return deepRecursion(n - 1); // Each call pushes a frame.
}
deepRecursion(1000000); // On most systems: stack overflow.Stack space is the space complexity
From the last lesson, you saw that factorial(n) has O(n) space complexity. Now you understand why: each recursive call pushes a frame. The deepest recursion is n levels, so the stack holds up to n frames at once. That is O(n) stack space.
If you call factorial(1000), 1000 frames accumulate before the deepest call returns. If you call factorial(1000000), the stack overflows long before you finish.
Recursion can be rewritten iteratively
Every recursion can, in principle, be rewritten with an explicit loop and an explicit stack (or other data structure). Recursion is a convenience: the call stack does the bookkeeping for you. But if the stack space is a problem, you can trade recursion for iteration + manual stack management.
Example: compute factorial iteratively (no recursion).
function factorialIterative(n) {
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}This uses O(1) space—no recursive calls, just a single loop. But it is less intuitive when the problem is naturally recursive (like tree traversal).
Let us code recursion and see the call stack in action.
Example 1: Factorial (calls grow and shrink)
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120Example 2: Deep recursion (approaches stack limits)
function countDown(n) {
if (n === 0) {
console.log("Done!");
return;
}
console.log(n);
countDown(n - 1);
}
countDown(10); // Prints 10, 9, 8, ..., 1, then "Done!"
countDown(100000); // Likely stack overflow on most systemsExample 3: Iterative version (uses O(1) space)
function countDownIterative(n) {
for (let i = n; i >= 1; i--) {
console.log(i);
}
console.log("Done!");
}
countDownIterative(10);
countDownIterative(100000); // No problem: uses only a loop, no stack framesTry all three:
Let us trace factorial(4) frame by frame, showing the stack grow to the deepest point, then shrink.
1
function factorial(n) {
2
if (n <= 1) return 1;
3
return n * factorial(n - 1);
4
}
Time: For factorial(n), there are n recursive calls, each doing O(1) work (one multiplication, one comparison). Total: O(n) time.
Space (the call stack): Each call pushes a frame. The deepest recursion reaches depth n, so the stack holds at most n frames. Total: O(n) space on the call stack. This is the key insight: the space complexity of a recursive function is determined by the maximum recursion depth.
Practical limits: On most systems, the stack is 1–8 MB. Each frame costs roughly 100–500 bytes (depending on how many local variables and parameters a function has). This means a recursion depth of 10,000–80,000 is usually safe, but 1,000,000 is not.
Comparison with iteration: An iterative loop doing the same computation uses O(1) stack space—no recursive calls. But recursive code is often cleaner and more intuitive when the problem has recursive structure (trees, divide-and-conquer).
The tradeoff: recursion is elegant but costly in space; iteration is efficient but sometimes less intuitive.
What is the maximum stack depth when you call factorial(7)?
In the call stack, which frame is at the top (the next to execute)?
What is the space complexity of factorial(n)?
Why does factorial(1000000) likely crash with a stack overflow?
When you call sumTo(4), order the frames that appear on the stack (from bottom to top at the deepest point):
- Frame for sumTo(4)
- Frame for sumTo(3)
- Frame for sumTo(2)
- Frame for sumTo(1)
Common mistake
Mistake: Underestimating stack depth. You write a recursive function and think “it will only go a few levels deep,” but then you pass a large input:
function process(arr, i = 0) {
if (i >= arr.length) return;
process(arr, i + 1);
}
process(new Array(100000)); // Stack overflow!The function recursively calls itself once per array element. With 100,000 elements, that is 100,000 frames. The stack cannot hold that many. Always think about the maximum recursion depth and the input size together.
Common mistake
Mistake: Forgetting that recursion costs stack space. You remember that a function is O(n) in time but forget that it is also O(n) in stack space:
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(100); // Stack overflow, even though it might look "simple"This function is also slow (exponential time), but the point here is: each recursive call eats stack memory. If you do not have explicit control over how deep the recursion goes, you can hit the stack limit. For fibonacci, an iterative approach or memoization (storing answers) is far better.
You write a recursive function that counts from 1 to n. On your computer, it crashes with 'stack overflow' when n = 50,000. What does this tell you?
The call stack is where the computer keeps track of function calls.
Stack frame: When a function is called, a frame is created and pushed onto the stack. The frame holds the parameters, local variables, and return address. When the function returns, the frame is popped.
Recursion and the stack: Each recursive call pushes a new frame. The deepest recursion has a stack depth equal to the number of nested calls. For factorial(n), the depth is n.
Space complexity: The recursion depth translates to stack space. A recursion that goes n deep uses O(n) stack space, even if the algorithm does nothing else.
Stack overflow: The stack has a finite size (usually 1–8 MB). If recursion goes deeper than the available space, a stack overflow occurs and the program crashes. This is a hard limit.
Iterative alternative: Any recursion can be rewritten with a loop and explicit data structure (a manual stack). Iteration uses O(1) stack space but is sometimes less intuitive.
When recursion fits: Use recursion when the problem naturally breaks into recursive subproblems (trees, divide-and-conquer, backtracking). If the input is so large that the stack depth would overflow, rewrite iteratively.