RangeError: Maximum call stack size exceeded

Quick answer

The call stack overflowed — almost always infinite or too-deep recursion. Add a base case that's actually reached, watch for accidental self-calls (getters/setters, event re-dispatch, React render loops) and circular references, and for very deep data convert the recursion to iteration with an explicit stack.

The exact error string

function f() { return f(); }
f();
// Uncaught RangeError: Maximum call stack size exceeded

// Node prints a long repeated stack trace ending in:
// RangeError: Maximum call stack size exceeded

What it actually looks like

Chrome DevTools console showing Uncaught RangeError: Maximum call stack size exceeded followed by the frame at walk repeated many times at the same line and column.
Captured from Chrome 152. The diagnosis is the shape of the stack, not the message: one frame repeating at an identical line:column is the call that never reaches its base case. Where the repeating block cycles between two or three different frames instead, you have mutual recursion.

How to find the recursion

A stack overflow trace is the diagnosis: the same one or two frames repeat hundreds of times — that repeating frame is your loop, and the bottom of the trace is where it started. If the trace is truncated, widen it before you re-run:

Error.stackTraceLimit = 100;     // show more frames to spot the cycle
// or run: node --stack-trace-limit=100 app.js

// is it direct or indirect? log on entry to suspects:
function walk(n) { console.count("walk"); /* ... */ walk(next); }
// a runaway count pinpoints the function that never returns

Cause 1: missing or unreachable base case

// ❌ no base case — recurses forever
function countdown(n) { console.log(n); countdown(n - 1); }

// ✅ stop when the condition is met, and move toward it
function countdown(n) {
  if (n < 0) return;             // base case
  console.log(n);
  countdown(n - 1);              // progresses toward the base case
}

Cause 2: accidental self-call

const obj = {
  get value() { return this.value; }   // ❌ getter reads itself -> recursion
};

el.addEventListener("click", function handler() {
  el.click();                          // ❌ re-dispatches the same event
});

Cause 3: circular references when walking objects

function walk(node, seen = new Set()) {
  if (seen.has(node)) return;          // ✅ stop on a cycle
  seen.add(node);
  for (const k in node) {
    if (node[k] && typeof node[k] === "object") walk(node[k], seen);
  }
}

Cause 4: deep but finite recursion → use iteration

// iterative depth-first traversal — no recursion, no stack limit
function walkIter(root) {
  const stack = [root];
  while (stack.length) {
    const node = stack.pop();
    for (const k in node) {
      if (node[k] && typeof node[k] === "object") stack.push(node[k]);
    }
  }
}

Worked example: recursing over nested JSON with a cycle

You walk a parsed JSON-like object to transform it, but an object references an ancestor (built in memory, or a graph serialized with refs), so the recursion never bottoms out:

const a = { name: "a" };
a.self = a;                       // a cycle

function deepCount(node) {        // ❌ overflows on the cycle
  let n = 1;
  for (const k in node) if (typeof node[k] === "object") n += deepCount(node[k]);
  return n;
}

function deepCount(node, seen = new Set()) {   // ✅ track visited
  if (seen.has(node)) return 0;
  seen.add(node);
  let n = 1;
  for (const k in node) if (node[k] && typeof node[k] === "object") n += deepCount(node[k], seen);
  return n;
}

Inspect a suspect structure in the JSON Formatter first — and remember that JSON.stringify on a cycle throws the clearer Converting circular structure to JSON, not this error.

In React: render loops

// ❌ setState during render re-triggers render endlessly
function C() {
  const [n, setN] = useState(0);
  setN(n + 1);                    // -> "Too many re-renders" / stack overflow
}

// ❌ effect with a dependency it also updates, every render
useEffect(() => { setN(n + 1); }, [n]);

// ✅ update in response to an event or a one-time effect
useEffect(() => { setN(1); }, []);   // runs once

How deep the stack actually is (and why the number moves)

There is no fixed limit to look up. The stack is a fixed byte budget, so the number of frames you get depends on how large each frame is — measured on Node v24.15.0:

let d = 0;
const f = () => { d++; f(); };
try { f(); } catch (e) { console.log(d); }     // ≈ 12,473 frames

let d2 = 0;
const g = (n) => { d2++; return n ? g(n - 1) : 0; };
try { g(1e6); } catch (e) { console.log(d2); } // ≈ 10,394 frames — one argument, fewer frames

Two useful consequences. First, the depth is not a number to code against: the same function reaches a different depth after a refactor that adds a local variable, which is why a recursion that "always worked" can start overflowing on data it used to handle. Second, and more practically, a few thousand frames is nowhere near enough for per-item recursion — if you are recursing once per element of a collection, you will overflow on a list that a loop would handle without noticing.

If you genuinely need deeper recursion in Node, --stack-size raises the budget, but treat it as a diagnostic rather than a fix: it moves the cliff without removing it, and setting it beyond what the OS thread actually allocated crashes the process outright instead of throwing a catchable RangeError.

Common variants of this message

CauseTell-taleFix
missing base caseone frame repeatsadd a reachable base case
circular objectrepeats while walking datatrack visited in a Set
getter/setter self-refframe is a property accessorbreak the self-reference
deep finite recursiononly fails on large inputconvert to iteration
React render loop"Too many re-renders"don't setState during render
JSON.stringify on a cycledifferent messagesee circular structure

Debugging checklist

Frequently Asked Questions

How do I find the function that's recursing?

Look at the stack trace: a stack overflow shows the same one or two frames repeated hundreds of times — that repeating frame is the loop. The bottom of the trace shows where it started. If it's truncated, set a higher trace limit (Error.stackTraceLimit) or step through in the debugger to see the cycle.

Is this the same as a stack overflow?

Yes. 'Maximum call stack size exceeded' is JavaScript's stack overflow: too many nested function calls that never return. Each call uses a stack frame, and the engine has a fixed limit (a few thousand to tens of thousands of frames depending on the engine and frame size).

Why does it appear with JSON.stringify?

Usually it doesn't — JSON.stringify on a circular object throws a different, clearer error: 'Converting circular structure to JSON'. If JSON.stringify overflows the stack instead, the structure is extremely deeply nested rather than circular; serialize in chunks or flatten it.

How is this different from React's 'Too many re-renders'?

They're related infinite loops with different messages. 'Too many re-renders' comes from calling setState during render; 'Maximum call stack size exceeded' is a function calling itself synchronously. A setState in render or a missing useEffect dependency can cause either, depending on timing — both are fixed by breaking the self-trigger.

Will increasing the stack size fix it?

Rarely, and only for genuinely deep but correct recursion. node --stack-size=... raises the limit but just delays the overflow and can crash the process harder. Fix the base case or convert to iteration; reserve a larger stack for cases you truly can't refactor.

Why does it only crash on large inputs?

Correct recursion still overflows once the depth exceeds the stack limit. Small inputs stay under it; a deeply nested object, a long linked list, or a big tree pushes past it. Convert that algorithm to an iterative version with an explicit stack so depth is bounded by heap memory, not the call stack.

References

Recursing over JSON data?

Inspect deeply-nested or self-referencing structures in the formatter before you walk them — nothing is uploaded to a server.

JSON Formatter JSON Diff All Error References
About the author

Pasindu Ishan is a Senior Software Engineer working on data streaming systems, which means most days end at a JSON payload that will not parse. The JavaScript references here are checked against a real runtime before being written up. He builds the free in-browser tools at JSON Dev Tools.