Module P-1·16 min read

Base case vs recursive case, how the call stack unwinds, and when recursion beats — or loses to — iteration.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module P-1 — Recursion and the Call Stack

What this module covers: Practitioner opens here deliberately — the modules right after this one (Linked Lists, Tree Traversal, Backtracking) all lean on recursion, and understanding it properly means understanding the call stack (Module F-6, Module F-12) that recursion runs on. This module covers the base case/recursive case shape, how recursive calls actually consume stack memory, when recursion is the wrong tool, and why JavaScript can't optimize away deep recursion the way some languages can.


Every Recursive Function Has Two Parts

A recursive function calls itself, but it must always have a base case — a condition where it stops calling itself and returns directly — and a recursive case, where it calls itself with a smaller version of the problem.

typescript

Miss the base case, or fail to make genuine progress toward it (calling factorial(n) again instead of factorial(n - 1), say), and the function never stops — Module F-6 already showed exactly what that produces: a RangeError: Maximum call stack size exceeded, because every call pushes another frame with nothing ever popping them off.


What Actually Happens on the Call Stack

Every recursive call is a real function call — it pushes a real stack frame (Module F-6), holding that call's arguments and local variables, exactly like any other function call. factorial(4) doesn't "loop" — it builds a stack four frames deep, then unwinds it:

text

Each level waits, mid-execution, for the level below it to return before it can finish its own multiplication — that's why the frame has to stay on the stack rather than being popped immediately. This is also the direct answer to "how much memory does recursion use": a recursive function that goes n levels deep before hitting its base case uses O(n) space on the call stack, exactly as flagged back in Module F-1 — even though it never allocates a single array or object explicitly.

Analogy: Recursion is a stack of unopened envelopes, each containing instructions and a smaller, sealed envelope inside. You can't act on envelope 1 until you've opened envelope 2, which you can't act on until you've opened envelope 3, and so on — you keep opening inward until you hit one with no envelope inside (the base case), then you work your way back out, resolving each one in turn.


The Trap: Naive Exponential Recursion

Not every recursive function costs O(n) space and O(n) time. The classic naive Fibonacci implementation costs vastly more, because it doesn't just recurse deep — it recurses wide, recomputing the same values repeatedly:

typescript

Every call (except the base cases) makes two further calls, and those calls overlap heavily — fibNaive(5) calls fibNaive(4) and fibNaive(3), but fibNaive(4) also calls fibNaive(3) internally, recomputing it from scratch. The number of calls roughly doubles per level of depth, which is exactly the O(2ⁿ) exponential class introduced in Module F-1's complexity table — at n = 40, this is well over a billion calls, and the function that looks like "clean, simple recursion" in a textbook will hang a real process for a very long time.

Module P-11 (Dynamic Programming I) picks up exactly here: memoization — remembering results already computed, the same "have I seen this?" idea from Module F-4's hash maps — collapses this from O(2ⁿ) back down to O(n), without changing the recursive shape of the function at all.


When Recursion Wins, and When It Doesn't

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.