Maximum Call Stack Size Exceeded (tested)
RangeError: Maximum call stack size exceeded means a function called itself (directly or through other functions) so many times that the call stack ran out of room. It is almost always a recursive function with a missing or broken base case. Unlike an out-of-memory crash, this one is a normal catchable exception.
1. The exact error
A function that calls itself with no way to stop:
function recurse(n) { return recurse(n + 1); }
recurse(0);
Run with plain Node.js, this crashes the process:
RangeError: Maximum call stack size exceeded
at recurse (file.mjs:1:23)
at recurse (file.mjs:1:30)
at recurse (file.mjs:1:30)
...
Node.js v24.21.0
We measured the same recurse(n+1) function in headless Chromium 153 (via Playwright, using page.evaluate). The message was identical: RangeError: Maximum call stack size exceeded, with no engine-specific wording difference. Both Node and Chromium run on V8, which explains the match; other engines (Firefox's SpiderMonkey, Safari's JavaScriptCore) use different wording per MDN docs (e.g. Firefox says "too much recursion"), but we did not measure those.
2. It IS catchable, unlike heap-out-of-memory
This is the key practical fact: a stack overflow throws a normal JavaScript exception. You can try/catch it and keep running.
function recurse(n) { return recurse(n + 1); }
try {
recurse(0);
} catch (e) {
console.log(e.name + ':', e.message); // RangeError: Maximum call stack size exceeded
console.log(e instanceof RangeError); // true
}
We confirmed e instanceof RangeError is true and the process does not exit. This is very different from a JS heap exhaustion crash, which throws a fatal error the process usually cannot recover from — see JavaScript heap out of memory for that contrast, and exit code 137 if your process is instead being killed by the OS (a different problem: memory limit, not call stack).
3. Common real-world cause: mutual recursion with no termination
The bug is not always a single self-calling function. Two functions that call each other, each assuming the other one checks the exit condition, overflow the same way:
function a(n) { return b(n + 1); } // forgot to check n here
function b(n) { return a(n + 1); } // and forgot here too
a(0);
We ran this and got the identical RangeError: Maximum call stack size exceeded. The stack trace alternates between a and b, which is the tell that it's mutual recursion rather than simple self-recursion — scan the trace for a repeating two-function pattern instead of one name repeated.
The fix is always the same shape: make sure every recursive path has a base case that returns without recursing, and double check it for every function involved when recursion is mutual.
function factorial(n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1);
}
4. Does increasing the stack size help?
Node exposes --stack-size (in KB) to grow the call stack before V8 throws. We measured how many recursive frames a simple function could reach before throwing:
| Flag | Frames reached before RangeError |
|---|---|
| (default) | ~10,437 |
--stack-size=2000 | ~21,274 |
--stack-size=8000 | ~85,274 |
--stack-size=65500 | process segfaulted (exit code 139), no RangeError at all |
So the flag does raise the ceiling roughly proportionally. But we measured that pushing it too high (65500 KB) made Node crash with a segmentation fault instead of a clean RangeError — V8's internal limit no longer matched the OS thread's real stack size, and the process died harder, not safer. Treat --stack-size as a narrow workaround for one known-deep-but-finite algorithm, never a fix for unbounded recursion, and test any value you pick before relying on it.
5. How to actually fix it
- Add or fix the base case. This is the cause in the large majority of cases: a condition that should stop recursion is missing, inverted, or unreachable for some inputs.
- Check mutual recursion carefully. With two or more functions calling each other, verify the base case is reachable from every entry point, not just one.
- Convert deep recursion to a loop. JavaScript has no guaranteed tail-call optimization in V8, so a genuinely deep (not infinite) recursive algorithm over large input should be rewritten iteratively with an explicit stack/queue array.
- Check for accidental re-entry. A common trigger is a function that, inside a callback or event handler, ends up calling itself indirectly — e.g. a setter that triggers a watcher that calls the setter again.
- Catch it at a boundary if needed. Since it's a normal
RangeError, you can wrap a risky recursive call intry/catchas a safety net while you find the real cause — but treat that as temporary, not the fix.
Frequently asked questions
Is 'Maximum call stack size exceeded' the same as an out-of-memory crash?
No. We measured it as a normal, catchable RangeError (e instanceof RangeError is true) that does not end the process by itself. A JS heap out-of-memory crash is fatal and the process cannot recover; see our JavaScript heap out of memory guide for that case.
What's the most common cause?
A recursive function with a missing, wrong, or unreachable base case. We reproduced it with a one-line self-calling function and with two functions calling each other (mutual recursion) when neither checked a stopping condition.
Does the error message differ between Node.js and the browser?
We tested Node.js 24 and headless Chromium 153 and got the exact same message, 'RangeError: Maximum call stack size exceeded', because both run on V8. Per MDN docs (not measured by us), Firefox instead says 'too much recursion' and Safari/JavaScriptCore has its own wording.
Does Node's --stack-size flag fix infinite recursion?
No. We measured it only raising how many frames you get before the same RangeError (about 10,400 frames by default vs about 85,000 at --stack-size=8000). It's for genuinely deep but finite recursion, not infinite recursion. We also measured that setting it too high (65500) made Node segfault (exit code 139) instead of throwing a clean error.
Can I just catch the error and move on?
You can catch it; we confirmed try/catch works and the process keeps running afterward. But it's a symptom, not a fix. Find and correct the missing base case instead of relying on the catch.
Written with AI assistance. Measured on 2026-10-02 with Node.js v24.21.0 and headless Chromium 153 via Playwright 1.63 on the same box. Browser wording for other engines (Firefox, Safari) is per MDN docs, not measured. PlainHeaders is our extension; Node.js and Chromium are free.