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
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
Common variants of this message
| Cause | Tell-tale | Fix |
|---|---|---|
| missing base case | one frame repeats | add a reachable base case |
| circular object | repeats while walking data | track visited in a Set |
| getter/setter self-ref | frame is a property accessor | break the self-reference |
| deep finite recursion | only fails on large input | convert to iteration |
| React render loop | "Too many re-renders" | don't setState during render |
JSON.stringify on a cycle | different message | see circular structure |
Debugging checklist
- ✓ Read the repeated frames — they name the looping function
- ✓ Recursive? Add a base case and ensure each call moves toward it
- ✓ Getter/setter or event handler calling itself? Break the self-reference
- ✓ Walking objects? Track visited nodes in a
Setto stop cycles - ✓ Deep but valid data? Convert recursion to iteration (explicit stack)
- ✓ React? Don't
setStateduring render; fix effect dependencies - ✓
JSON.stringifyoverflow? That's a circular structure, a different error
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.
Recursing over JSON data?
Inspect deeply-nested or self-referencing structures in the formatter before you walk them — nothing is uploaded to a server.