Back

Fixing 'Maximum call stack size exceeded' in JavaScript

Fixing 'Maximum call stack size exceeded' in JavaScript

You’re staring at your console, and there it is: RangeError: Maximum call stack size exceeded. In Firefox, it might say too much recursion. Either way, your application just crashed, and you need to understand why—and how to fix it properly.

This error signals a stack overflow: your code has made so many nested function calls that the JavaScript engine ran out of space to track them. Let’s explore what triggers this in modern JavaScript, how to debug it effectively, and how to prevent it in React, Next.js, and Node.js applications.

Key Takeaways

  • The call stack has a finite size that varies by environment, so never write code that relies on hitting a specific limit.
  • Common causes include infinite recursion, mutual recursion, React infinite re-renders, and circular references in JSON serialization.
  • Debug by enabling “Pause on Exceptions” in DevTools and looking for repeating function patterns in the call stack.
  • Fix structurally by converting recursion to iteration, using trampolining, or handling circular references with a replacer function.

What the Call Stack Actually Does

Every time a function executes, the JavaScript engine creates a stack frame containing the function’s local variables, arguments, and return address. These frames stack on top of each other. When a function returns, its frame gets removed.

The problem? This stack has a finite size. The exact limit varies by environment—Chrome might allow around 10,000-15,000 frames, while Firefox permits roughly 50,000. Node.js typically caps around 11,000 frames by default.

Important: These numbers are implementation-dependent and can change between versions. Don’t write code that relies on hitting a specific limit.

Common Patterns That Trigger Stack Overflow

Classic Infinite Recursion

The textbook case: a function that calls itself without a proper exit condition.

function processItem(item) {
  // Missing base case
  return processItem(item.child)
}

Mutual Recursion

Two functions calling each other in a loop:

function isEven(n) {
  return n === 0 ? true : isOdd(n - 1)
}

function isOdd(n) {
  return n === 0 ? false : isEven(n - 1)
}

isEven(100000) // Stack overflow

React Infinite Re-renders

This is where many frontend developers encounter the error. State updates during render create infinite loops:

function BrokenComponent() {
  const [count, setCount] = useState(0)
  setCount(count + 1) // Triggers re-render immediately
  return <div>{count}</div>
}

Bad useEffect dependencies cause similar problems:

useEffect(() => {
  setData(transformData(data)) // data changes, effect runs again
}, [data])

JSON Circular Reference Stack Overflow

When objects reference themselves, JSON.stringify recurses infinitely:

const obj = { name: 'test' }
obj.self = obj
JSON.stringify(obj) // Maximum call stack size exceeded

Debugging Call Stack Overflow in Node.js and Browsers

Step 1: Enable “Pause on Exceptions”

In Chrome DevTools, open the Sources panel and enable “Pause on caught exceptions.” For Node.js, use the --inspect flag and connect Chrome DevTools.

Step 2: Inspect the Call Stack for Repeating Frames

When the debugger pauses, examine the call stack panel. Look for repeating patterns—the same function appearing dozens or hundreds of times indicates your recursion point.

Step 3: Use Async Stack Traces

Modern DevTools show async stack traces by default. This helps when the recursion spans Promise chains or setTimeout callbacks.

console.trace() // Prints current stack trace

Note: Increasing stack size with node --stack-size is a diagnostic tool, not a solution. It delays the crash but doesn’t fix the bug.

Practical Fixes That Actually Work

Convert Recursion to Iteration

Most recursive algorithms can become iterative with an explicit stack:

function processTree(root) {
  const stack = [root]
  while (stack.length > 0) {
    const node = stack.pop()
    process(node)
    if (node.children) {
      stack.push(...node.children)
    }
  }
}

Use Trampolining for Deep Recursion

Trampolines break recursion into steps, preventing stack growth:

function trampoline(fn) {
  return function(...args) {
    let result = fn(...args)
    while (typeof result === 'function') {
      result = result()
    }
    return result
  }
}

Handle Circular References Safely

For JSON serialization, use a replacer function or libraries like flatted:

const seen = new WeakSet()
JSON.stringify(obj, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    if (seen.has(value)) {
      return '[Circular]'
    }
    seen.add(value)
  }
  return value
})

Prevent Infinite Recursion in React

Always ensure useEffect dependencies are stable and state updates are conditional:

useEffect(() => {
  if (!isProcessed) {
    setData(transformData(rawData))
    setIsProcessed(true)
  }
}, [rawData, isProcessed])

Why Tail Call Optimization Won’t Save You

ES6 specified proper tail calls, which would theoretically allow infinite recursion in tail position. In practice, only Safari implements this. Chrome and Firefox do not, and Node.js disabled it. Don’t rely on TCO—refactor your code instead.

Conclusion

The “Maximum call stack size exceeded” error is a high-severity bug requiring structural fixes. You can’t catch your way out of it, and you shouldn’t try to increase stack limits in production.

Find the repeating pattern in your stack trace, then either add proper termination conditions, convert to iteration, or break work into async chunks. Treat circular references as data structure problems, not serialization problems.

When you see this error, your code is telling you something fundamental needs to change.

FAQs

Technically yes, but it's not a reliable solution. By the time this error throws, your application is in an unstable state. The stack is exhausted, and catching the error doesn't restore it. Fix the underlying recursion problem instead of trying to handle the exception.

Open your browser's DevTools, go to the Sources panel, and enable Pause on Exceptions. When the error occurs, examine the call stack panel for repeating function names. The function that appears repeatedly is your culprit. You can also use console.trace() to print the stack at specific points.

Calling setState directly in the render body triggers an immediate re-render, which calls setState again, creating an infinite loop. Move state updates into useEffect hooks with proper dependencies, or into event handlers. Never update state unconditionally during render.

In Node.js, you can use the --stack-size flag to increase the limit, but this only delays the crash. Browsers don't allow stack size changes. Neither approach fixes the root cause. Refactor your code to use iteration or async patterns instead of relying on deep recursion.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay