Engrain by ByteRush

JS.07.04 Junior· 10 minCore — asked often

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.

Each recursive call is one stack frameStep 1 of 5

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
    1. We call count([1, [2, [3]]]). One frame goes on the call stack.
    2. It finds the inner array [2, [3]] and calls itself. A second frame goes on top.
    3. Again for [3]. Three frames now wait on the stack.
    4. [3] holds no arrays: the base case. It returns 1 and its frame is removed.
    5. 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?

    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.”

    Stays on your device. Nothing is uploaded.
    Follow-up Does JavaScript have tail-call optimization?
    The ES2015 spec requires proper tail calls in strict code, but only Safari implements them. Chrome, Node.js and Firefox do not, so you cannot rely on it.
    Follow-up How do you avoid a stack overflow on deep input?
    Use an explicit stack (an array) and a loop. The array lives in heap memory, which is far larger than the call stack.
    Follow-up Recursion or a loop: which do you pick?
    Recursion when the data is a tree of known, modest depth: it is clearer. A loop with a stack when depth can be large or comes from users.

    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.
    • flattenIterative handles an array nested 100,000 levels deep.
    Your solution
    Ctrl/⌘ + Enter

    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
    The stack limit is not in the spec. It depends on the engine and the frame size. In Node.js 25, an empty function overflows after about 10,000 calls, and count after about 5,000.
    A tail call is a call that is the very last thing a function does, such as 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
    For input you do not control, such as uploaded JSON or a user-built tree, use a loop and an array as the stack. Push children, pop one, repeat until the array is empty. For a breadth-first walk, use a queue instead.
    Source: MDN: Recursion
    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?