C · Novice · 14 min

Recursion and the Call Stack

A recursive function is an implicit stack. Base cases, depth, and the n = 2·10⁵ stack overflow are the same lesson as writing the stack yourself.

Recursion is not a different algorithm from a loop-plus-stack. Each call pushes a frame: the arguments, the local variables, and the address to resume when the callee returns. When the callee returns, that frame pops. DFS on a tree, backtracking, and divide-and-conquer are this motion.

The difference is who owns the stack. The language owns the call stack. You own an explicit vector / array used as a stack. Same LIFO. Different failure modes.

Linear walk — n frames live at the deepest pointjs
function walk(i, a) {
  if (i === a.length) return 0; // base: empty suffix
  return a[i] + walk(i + 1, a);
}

Read the base case out loud: the suffix starting at `i` is empty when `i === n`. That is an off-by-one waiting to happen. i > n is one step too late (a[n] is already undefined). i === n - 1 as a base returns without adding the last element. Name the range the same way you name an array slice: this function owns a[i..n) — half-open, last included index is n - 1.

TRACE

a = [3, 1, 4]. Trace walk(0, a). Watch the call stack, not the arithmetic.

walk(0) pushes

Frame 0: i = 0, waiting on walk(1). The 3 is not added yet. Depth = 1.

i3
11
24

stack: [i=0]

1 / 5

CHECK

walk(i) returns 0 when i === a.length, otherwise a[i] + walk(i+1). You write the base as if (i > a.length). What happens?

Same DFS, explicit stack — depth is a vector you allocatejs
function dfsIter(start, adj) {
  const stack = [start];
  const seen = new Set([start]);
  while (stack.length) {
    const u = stack.pop();
    for (const v of adj[u]) {
      if (seen.has(v)) continue;
      seen.add(v);
      stack.push(v);
    }
  }
}

The explicit stack lives on the heap. A vector of 2·10⁵ ints is a rounding error against a 256 MB limit. The call stack is not that budget. When a solution is 'DFS on the implicit graph of size n' and n is 2·10⁵, write the iterative version, or recurse on a tree after you have confirmed the height is O(log n) (balanced) — not after you have confirmed the *graph* has n nodes.

Matching brackets is the same LIFO. Valid Parentheses in the arena is an explicit stack: push openers, pop on a closer. A recursive descent parser would use the call stack for the same nesting. If the string length is 10⁵, you still want the array.

CHECK

A tree with n = 2·10⁵ nodes is a single path. You DFS recursively from the root with a parent argument. Time limit 2s, memory 256 MB. What should you expect?

The Fibonacci you must not ship — depth n, work φⁿcpp
int fib(int n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2); // TLE for n ≳ 40
}

CHECK

n ≤ 40, compute the n-th Fibonacci number. Someone pastes the two-line recursive formula. Why is that the wrong recursion lecture?

Checks 0/3

Next lesson