javascriptseverity: critical
RangeError: Maximum call stack size exceeded

JavaScript RangeError: Maximum call stack size exceeded — Infinite recursion

RangeError: Maximum call stack size exceeded

92% fixable~15 mindifficulty: intermediate

Verified against MDN Web Docs — RangeError, V8 engine blog — tail call optimization, ECMAScript 2022 spec §14.9.1 · Updated June 2026

> quick_fix

Find the recursive function in the stack trace (it will repeat many times). Add or fix the base case, or convert the recursion to an iterative loop. For JSON.stringify circular reference errors, use a replacer function.

// BROKEN: no base case → infinite recursion
function countdown(n) {
    return countdown(n - 1); // never stops
}

// FIXED: base case added
function countdown(n) {
    if (n <= 0) return 0; // base case
    return countdown(n - 1);
}

// Iterative version for deep counts
function countdownIterative(n) {
    while (n > 0) n--;
    return 0;
}

// Circular reference in JSON.stringify
const obj = {};
obj.self = obj; // circular
// JSON.stringify(obj); // throws!
const safe = JSON.stringify(obj, (key, value) => {
    if (key === 'self') return undefined; // exclude circular ref
    return value;
});

What causes this error

RangeError: Maximum call stack size exceeded is JavaScript's equivalent of a StackOverflowError. It is thrown when the call stack — a fixed-size memory region that tracks function invocations — is exhausted. This happens with infinite recursion (no base case), mutual recursion (A calls B calls A with no exit), or very deep legitimate recursion (traversing a deeply nested object/tree). It also appears when JSON.stringify encounters a circular object reference.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Identify the recursive function from the stack trace

    The call stack trace shows the same function name repeated many times. That is the recursive function. Identify if it is: (1) a direct recursive call (function calls itself), (2) mutual recursion (A calls B calls A), or (3) a framework cycle (e.g., React rendering triggering state updates triggering re-renders).

  2. 02

    step 2

    Add or fix the base case

    Every recursive function must have a condition under which it returns without calling itself. Check: does the base case exist? Can the input ever reach it? If the recursion is `process(items)` and items is never reduced, the base case `if (items.length === 0)` is unreachable.

  3. 03

    step 3

    Convert deep recursion to iteration

    JavaScript engines do not perform tail-call optimization in practice (V8 removed it). For processing deep trees or lists, use an iterative approach with an explicit stack (array): `const stack = [root]; while (stack.length) { const node = stack.pop(); stack.push(...node.children); }`.

  4. 04

    step 4

    Fix circular reference in JSON.stringify

    If the error comes from JSON.stringify, there is a circular reference in the object. Use a replacer to skip circular refs, or use a library like `flatted` or `circular-json`. Alternatively, use `JSON.stringify(obj, getCircularReplacer())` with a WeakSet to track visited objects.

  5. 05

    step 5

    Increase Node.js stack size for deep but legitimate recursion

    As a temporary measure only: `node --stack-size=65536 app.js` increases the stack. This is not a fix — it only allows deeper recursion before the error recurs. Use it to confirm the problem is stack depth, then convert to iteration.

How to verify the fix

  • Run the same input — no RangeError should appear
  • Test with very deep inputs (1000+ levels) if the algorithm is recursive
  • Confirm JSON.stringify on the object produces valid output without errors

Why RangeError: Maximum call stack size exceeded happens at the runtime level

JavaScript engines allocate a fixed-size call stack region for each thread. Each function invocation pushes a frame containing local variables, arguments, and the return address onto this stack. The V8 engine's default stack size is approximately 984KB. When a recursive function exhausts this allocation before reaching a base case, V8 throws RangeError: Maximum call stack size exceeded rather than allowing the process to corrupt adjacent memory. Unlike the JVM, V8 does not implement tail-call optimization for most recursive patterns, meaning even a perfectly written tail-recursive function will overflow the stack on deep enough inputs.

Common debug mistakes for RangeError: Maximum call stack size exceeded

  • Forgetting the base case in a recursive tree traversal or data transformation function
  • An event listener that triggers a state update that triggers the same event — a React setState inside an event handler that causes a re-render that fires the same event
  • Calling JSON.stringify() on an Express.js request object or a Mongoose document that has circular references
  • A toString() or valueOf() method that calls another method that calls toString() — mutual recursion on built-in operations

When RangeError: Maximum call stack size exceeded signals a deeper problem

Maximum call stack exceeded in a JavaScript application is often a design signal: tree traversal and graph processing with arbitrary depth should be iterative, not recursive, because V8's stack is limited and TCO is not reliably available. React's rendering model has its own equivalent in deep component trees causing excessive reconciliation. The architectural pattern for deep recursive data processing in JavaScript is always the same: maintain an explicit work queue on the heap (an array acting as a stack or queue) rather than relying on the engine's call stack.

Editor's take

Maximum call stack size exceeded is JavaScript's stack overflow — the engine hit its recursion limit because a function called itself (or a cycle of functions called each other) without terminating. V8 (Chrome/Node) typically allows around 10,000-15,000 stack frames depending on how much memory each frame consumes. The error is not catchable in all contexts because the engine may be too deep to execute the catch block reliably. The most insidious production variant isn't obvious recursion like `function f() { f() }` — it's accidental infinite loops caused by getter/setter cycles, circular JSON serialization, or React component re-renders where a `useEffect` triggers a state change that triggers the same `useEffect`. In React specifically, this often manifests when a `useEffect` dependency array includes an object literal created during render — every render creates a new object reference, triggering the effect again. The fix for legitimate recursion is trampolining (returning a thunk instead of recursing directly) or converting to an iterative approach with an explicit stack array. For framework-induced cycles, the fix is breaking the circular dependency — `useMemo` for object references, `useCallback` for function references, or restructuring the state so the effect doesn't trigger its own dependency. If you see this in `JSON.stringify()`, you have a circular object reference — use a replacer function or a library like `flatted`.

By Bikram Nath · Curator · Updated June 2026

Frequently asked questions

How deep can the JavaScript call stack go?

It depends on the JavaScript engine and available memory. V8 (Node.js/Chrome) typically allows 10,000–15,000 frames for simple recursive calls. Functions with many local variables consume more stack space per frame. ES6 modules, async functions, and class constructors all add frames overhead.

Does JavaScript support tail-call optimization (TCO)?

The ES6 specification includes TCO for strict mode tail calls. However, V8 (Chrome/Node.js) removed its TCO implementation in 2016 due to performance and debugging concerns. Safari (JavaScriptCore) implements TCO. SpiderFox (Firefox) does not. In practice, do not rely on TCO in JavaScript — write iterative code for deep recursion.

What is a circular replacer for JSON.stringify?

A circular replacer uses a WeakSet to track visited objects and returns undefined for any object that has been seen before: `const getCircularReplacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) return; seen.add(value); } return value; }; };`

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.