Suggest an editImprove this articleRefine the answer for “Deep recursion”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**With deep recursion, that is when a function calls itself very many times before reaching the base case, JavaScript overflows the call stack and the engine throws `RangeError: Maximum call stack size exceeded`.** Every call creates a new execution context (a stack frame) holding local variables, arguments and the return address, and all of those frames stay on the stack until the base case is reached. The stack size is limited, usually around 10 000 to 20 000 nested calls, so you can blow it even with a correct base case. The fixes are rewriting the recursion as a loop or, for an asynchronous variant, splitting the work into chunks with `setTimeout`. ```javascript function countdown(n) { if (n === 0) return; countdown(n - 1); } countdown(100000); // RangeError: Maximum call stack size exceeded ``` **Key point:** recursion depth is limited by the stack size, not by whether a base case exists.Shown above the full answer for quick recall.Answer (EN)Image**With very deep recursion JavaScript overflows the call stack: the engine throws `RangeError: Maximum call stack size exceeded`.** Deep recursion means the function calls itself many times before reaching the base case, and all of those unfinished calls occupy the stack at once. ## Theory ### TL;DR - Every recursive call creates a new execution context (a stack frame) on the call stack. - A frame stores local variables, arguments and the return address. - The stack only "unwinds" once the base case has been reached. - The stack size is limited: usually around 10 000 to 20 000 nested calls. - Exceeding the limit gives `RangeError: Maximum call stack size exceeded`. - Even a correct base case does not help if the depth is too large. - The ways out: a loop, tail recursion (where it is optimized) or chunks scheduled with `setTimeout`. ### Quick example ```javascript function recurse(n) { console.log(n); recurse(n + 1); // no base case } recurse(1); ``` Output: ```javascript RangeError: Maximum call stack size exceeded ``` ### What happens under the hood Every time a function calls itself, JavaScript creates a **new execution context** (a stack frame) on the **call stack**: ```javascript factorial(5) -> factorial(4) -> factorial(3) -> factorial(2) -> factorial(1) ``` Every call stores: - local variables, - arguments, - the return address. Once the base case is reached, the stack "unwinds" back: the deepest call returns a value, its frame is freed, and so on up to the top. But if there are **too many** calls, the stack overflows before the unwinding can even start. The browser (or Node.js) allocates a limited stack size, usually around **10 000 to 20 000** nested calls. The exact number depends on the engine, the platform and even on how many arguments and local variables your function has: the heavier the frame, the fewer of them fit. ### Even with a base case you can hit the limit ```javascript function countdown(n) { if (n === 0) return; countdown(n - 1); } countdown(100000); // RangeError ``` Despite the base case being there, the recursion depth (100 000) is too large for the JavaScript stack. In other words, a base case protects you from **infinite** recursion but not from **deep** recursion. That is an important distinction in an interview: correct logic and a safe depth are two separate requirements. ### How to avoid the problem **1. Rewrite the recursion as a loop** ```javascript function countdown(n) { while (n > 0) n--; } ``` A loop keeps the state in a single variable and adds no frames at all, so depth stops being a constraint. **2. Use tail recursion (if the optimization existed)** ```javascript function countdown(n) { if (n === 0) return; return countdown(n - 1); // tail call } ``` A tail call is one where the recursive call is the last action of the function, so its frame is no longer needed and could in theory be reused. But most JavaScript engines **do not implement tail call optimization**, so you cannot rely on it: the code above still blows up. **3. Split the recursion into chunks with `setTimeout`** ```javascript function countdown(n) { if (n === 0) return; console.log(n); setTimeout(() => countdown(n - 1), 0); // does not grow the stack } ``` Here every call runs **in a new event loop iteration**: the previous call has already finished and its frame has been freed, so the stack does not grow. The price is that the function becomes asynchronous, so the result can only be returned through a callback or a `Promise`. **4. Move the state into your own stack array** ```javascript function collectValues(root) { const out = []; const stack = [root]; while (stack.length > 0) { const node = stack.pop(); if (!node) continue; out.push(node.value); for (const child of node.children ?? []) stack.push(child); } return out; } ``` The array lives on the heap rather than on the call stack, so the depth limit effectively disappears. ### Summary table | Phenomenon | What happens | | --- | --- | | Deep recursion | Many nested calls of the same function | | Result | Call stack overflow (`RangeError`) | | Why | Every call creates a new context on the stack | | How to avoid it | Use a loop, tail recursion or `setTimeout` | ### Common mistakes - **Confusing infinite recursion with deep recursion.** A base case saves you from the first but not from the second: `countdown(100000)` is logically correct and still crashes. - **Counting on tail call optimization.** It is in the specification, but mostly absent from real JavaScript engines, so rewriting code "in tail style" does not make it any safer. - **Treating the limit as a fixed number.** It depends on the engine, the platform and the frame size, so a test that passes on one machine can fail on another. - **Catching `RangeError` in a `try/catch` and calling it solved.** Catching hides the bug, but the work is still not done and the state may be left half updated. - **Forgetting that `setTimeout` makes the function asynchronous.** After that change `return` no longer hands a result to the caller; you need a callback or a `Promise`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.