The error RangeError: Maximum call stack size exceeded (also called a “stack overflow” in non-browser environments) means a function called itself (directly or indirectly) so many times that JavaScript ran out of call stack space. Here’s every cause and fix.
๐ Table of Contents
- Why This Happens
- Cause 1: Infinite Recursive Event Handler
- Cause 2: Circular Object References in JSON.stringify
- Cause 3: Deep Object Cloning with Recursion
- Cause 4: Accidentally Calling a Function Instead of Referencing It
- Fix: Convert Recursion to Iteration (Trampolining)
- Debugging Stack Overflows
- Frequently Asked Questions
- Conclusion
๐ Key Takeaway
The error RangeError: Maximum call stack size exceeded (also called a “stack overflow” in non-browser environments) means a function called itself (directly or indirectly) so many times that JavaScript ran out of call stack space. Here’s every cau…
Why This Happens
Every function call adds a frame to the call stack. JavaScript engines have a limited stack size (~10,000-15,000 frames depending on browser/Node version). When recursive functions lack a proper base case, or when two functions call each other indefinitely, the stack fills up and throws RangeError.
// Simplest example: missing base case
function factorial(n) {
return n * factorial(n - 1); // never stops โ factorial(0) calls factorial(-1) etc.
}
factorial(5); // RangeError: Maximum call stack size exceeded
// Fix: add base case
function factorial(n) {
if (n <= 1) return 1; // base case stops recursion
return n * factorial(n - 1);
}
Cause 1: Infinite Recursive Event Handler
// ๐ Bug: React onChange updates state, triggers re-render, fires onChange again
function SearchInput() {
const [value, setValue] = useState('');
return <input
value={value}
onChange={(e) => {
setValue(e.target.value);
// If something here triggers another state update that causes re-render...
}}
/>;
}
// ๐ Bug: useEffect calling function that updates state without dependency guards
useEffect(() => {
setData(processData(data)); // updates data โ triggers effect โ updates data...
}, [data]);
// โ
Fix: use functional update or conditional guard
useEffect(() => {
if (data.needsProcessing) {
setData(prev => processData(prev)); // functional update avoids dependency
}
}, [data.id]); // depend on stable identifier, not the object
Cause 2: Circular Object References in JSON.stringify
// ๐ Bug: circular reference crashes JSON.stringify
const obj = { name: "test" };
obj.self = obj; // circular reference
JSON.stringify(obj); // TypeError: Converting circular structure to JSON
// โ
Fix 1: Replace circular references
function stringifySafe(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
}
// โ
Fix 2: Use structuredClone for deep copying (handles circular refs)
const safeCopy = structuredClone(obj); // built-in, available Node 17+ / modern browsers
Cause 3: Deep Object Cloning with Recursion
// ๐ Bug: naive deep clone fails on deeply nested objects
function deepClone(obj) {
if (typeof obj !== 'object') return obj;
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, deepClone(v)]) // crashes at ~10k depth
);
}
// โ
Fix 1: Use structuredClone (built-in, iterative internally)
const clone = structuredClone(deeplyNestedObj);
// โ
Fix 2: Convert recursion to iteration for very deep objects
function deepCloneIterative(root) {
const stack = [{ source: root, target: Array.isArray(root) ? [] : {} }];
const result = stack[0].target;
const map = new WeakMap([[root, result]]);
while (stack.length) {
const { source, target } = stack.pop();
for (const [key, value] of Object.entries(source)) {
if (typeof value === 'object' && value !== null) {
if (map.has(value)) {
target[key] = map.get(value);
continue;
}
const newTarget = Array.isArray(value) ? [] : {};
map.set(value, newTarget);
target[key] = newTarget;
stack.push({ source: value, target: newTarget });
} else {
target[key] = value;
}
}
}
return result;
}
Cause 4: Accidentally Calling a Function Instead of Referencing It
// ๐ Bug: function immediately calls itself
class Timer {
start() {
this.intervalId = setInterval(this.tick(), 1000); // tick() CALLED, not referenced
// this.tick() returns undefined (or calls tick immediately, which schedules tick...)
}
tick() {
console.log('tick');
this.start(); // calls start() which calls tick() again immediately
}
}
// โ
Fix: pass function reference, not call result
class Timer {
start() {
this.intervalId = setInterval(() => this.tick(), 1000); // reference, not call
}
tick() {
console.log('tick');
}
}
Fix: Convert Recursion to Iteration (Trampolining)
// For recursive algorithms that process large inputs:
// ๐ Recursive sum โ crashes for large n
function sum(n) {
if (n === 0) return 0;
return n + sum(n - 1); // crashes for n > ~10,000
}
// โ
Iterative solution
function sumIterative(n) {
let total = 0;
for (let i = n; i > 0; i--) {
total += i;
}
return total;
}
// โ
Trampolining (for complex recursive algorithms that are hard to iterate directly)
function trampoline(fn) {
return function trampolined(...args) {
let result = fn(...args);
while (typeof result === 'function') {
result = result();
}
return result;
};
}
const sumTrampoline = trampoline(function sum(n, acc = 0) {
if (n === 0) return acc;
return () => sum(n - 1, acc + n); // returns a thunk instead of recursing
});
sumTrampoline(100000); // works fine
Debugging Stack Overflows
// The error stack trace shows the recursive call
// RangeError: Maximum call stack size exceeded
// at factorial (/app/math.js:2:10)
// at factorial (/app/math.js:2:10) โ same line repeated = infinite recursion
// at factorial (/app/math.js:2:10)
// ...
// Step 1: Look for the repeated line in the stack trace
// Step 2: Add console.log with a counter to see how deep it goes
let callCount = 0;
function myFunction(data) {
callCount++;
if (callCount > 1000) {
console.trace('Deep recursion detected at:', callCount);
throw new Error('Stack overflow prevention');
}
// ...
}
// Step 3: Check for circular dependencies
function detectCircular(obj, path = '', visited = new WeakSet()) {
if (typeof obj !== 'object' || obj === null) return;
if (visited.has(obj)) { console.log('Circular at:', path); return; }
visited.add(obj);
for (const [key, val] of Object.entries(obj)) {
detectCircular(val, `${path}.${key}`, visited);
}
}
Frequently Asked Questions
Q: What is the maximum call stack depth in JavaScript?
A: ~10,000-15,000 frames in Chrome/Node.js; ~5,000-10,000 in Firefox; varies by engine version and frame size. Exact numbers change between versions.
Q: Can I increase the call stack size?
A: In Node.js: node --stack-size=65536 app.js (size in KB). In browsers: not possible. Better solution: fix the recursion instead of increasing the limit.
Q: What’s the difference between a stack overflow and heap out of memory?
A: Stack overflow (RangeError) = too many function call frames. Heap out of memory (FATAL ERROR) = too many objects allocated. Stack overflow is from deep recursion; heap errors are from allocating too much data.
Q: How do I deep clone an object safely in 2026?
A: Use structuredClone(obj) โ built into all modern browsers and Node.js 17+. It handles circular references, Date objects, Maps, Sets, and ArrayBuffers correctly. Use JSON.parse(JSON.stringify(obj)) only for simple JSON-serializable objects without circular refs.
Q: Is tail call optimization available in JavaScript?
A: Theoretically yes (ES6 spec includes TCO), but only Safari implements it fully in 2026. V8 (Chrome/Node.js) removed TCO. Don’t rely on TCO for stack safety โ use iterative solutions or trampolining instead.
Conclusion
Maximum call stack size exceeded errors come from infinite or extremely deep recursion. The fix depends on the cause: add a base case to stop recursion, convert recursion to iteration for large inputs, use structuredClone for deep copies, and watch for circular object references. The stack trace always shows the repeated line causing the infinite recursion โ read it first and the fix is usually obvious.
๐ You might also like
๐ Share this article




โ๏ธ Leave a Comment