Question

JavaScript Maximum Call Stack Size Exceeded: Causes, Safari Fixes, and Timeouts

htmljavascriptwebkitcallstackdwr

Question

I am using a Direct Web Remoting (DWR) JavaScript library and receive an error only in Safari on desktop and iPad:

Maximum call stack size exceeded

What does this JavaScript error mean? Does it stop the current processing completely, and how can I fix it in Safari?

On iPad Safari, I instead see:

JS: execution exceeded timeout

Are these errors caused by the same issue?

Short Answer

You will learn how the JavaScript call stack works, why stack-overflow errors occur, why Safari timeout errors are related but not identical, and how to identify the code pattern causing each problem.

Concept

JavaScript runs function calls by placing them on a call stack. Each time one function calls another function, JavaScript records the new call. When that function finishes, its entry is removed.

A Maximum call stack size exceeded error means JavaScript tried to add more function calls than the engine allows. This most commonly happens because of unintended infinite recursion: a function keeps calling itself, directly or indirectly, without reaching a stopping condition.

function repeatForever() {
  repeatForever();
}

repeatForever();

The stack grows until the browser cannot add another call, so it throws a RangeError (often shown as “Maximum call stack size exceeded”).

This error stops the current synchronous execution path. Code after the failing call does not run unless the error is caught. However, other previously scheduled browser events may still run later; the entire page does not necessarily become permanently unusable.

Safari's stack limit can differ from Chrome's or Firefox's. Code that is already recursive or unusually deeply nested may therefore fail in Safari first. The correct fix is not to depend on a larger browser limit; it is to remove unnecessary recursion or process work in smaller pieces.

JS: execution exceeded timeout is different. It usually means a script ran continuously for too long and the browser stopped or warned about it to keep the page responsive. An infinite loop, very expensive calculation, or recursion that does substantial work before overflowing can cause this. Both errors can have the same root cause—such as work that never ends—but a timeout is about elapsed execution time, while a stack overflow is about too many nested function calls.

Mental Model

Think of the call stack as a stack of paper notes on a desk.

  • Calling a function means putting a new note on top.
  • Returning from a function means removing the top note.
  • A recursive function that never stops keeps adding notes.
  • Eventually, the stack is too tall, so JavaScript refuses to add another note.

A timeout is like a supervisor noticing that one task has occupied the desk for too long. Even if the stack is not full, the task may be stopped because it prevents other work—such as taps, scrolling, and rendering—from happening.

Take Quiz

Syntax and Examples

A recursive function needs a base case: a condition that returns without making another recursive call.

function countDown(number) {
  if (number <= 0) {
    console.log("Done");
    return;
  }

  console.log(number);
  countDown(number - 1);
}

countDown(3);

Output:

3
2
1
Done

The if (number <= 0) branch is the base case. It prevents recursion from continuing forever.

A common indirect recursion bug occurs when two functions call each other:

function start() {
  continueWork();
}

function continueWork() {
  start();
}

start();

Neither function can return because each calls the other first. Fix it by adding a meaningful stopping condition or by changing the design so the functions do not repeatedly call each other.

For many repeated tasks, use iteration instead of recursion:

function countDownWithLoop(number) {
  for (let current = number; current > 0; current -= 1) {
    console.log(current);
  }

  console.log("Done");
}

countDownWithLoop(3);

A loop generally uses a fixed amount of call-stack space, even when it processes many items.

Step by Step Execution

Consider this safe recursive example:

function sumTo(number) {
  if (number === 0) {
    return 0;
  }

  return number + sumTo(number - 1);
}

const total = sumTo(3);
console.log(total);

Execution trace:

  1. sumTo(3) is called. It needs 3 + sumTo(2).
  2. sumTo(2) is called. It needs 2 + sumTo(1).
  3. sumTo(1) is called. It needs 1 + sumTo(0).
  4. sumTo(0) reaches the base case and returns 0.
  5. sumTo(1) returns 1 + 0, which is 1.
  6. sumTo(2) returns 2 + 1, which is 3.
  7. sumTo(3) returns 3 + 3, which is 6.
  8. console.log(total) prints 6.

Each call eventually returns, so stack entries are removed. Without the number === 0 base case, calls would continue with -1, -2, and so on until a stack overflow occurred.

Real World Use Cases

Recursion is useful when a problem naturally has nested structure or can be divided into smaller versions of itself:

  • Nested menu rendering: Render menu items and their child menus.
  • Tree traversal: Visit nodes in a folder tree, comment thread, or organization chart.
  • Nested API data: Read categories that contain subcategories.
  • DOM traversal: Visit an element and then its child elements.
  • Parsing: Process nested expressions such as parentheses in a programming language.

For large flat collections—such as thousands of records from an API—loops and array methods are usually safer than deep recursion.

For browser timeout prevention, split expensive work into chunks so the browser can process user input and redraw the page between chunks. For example, schedule later chunks with setTimeout, requestAnimationFrame, or requestIdleCallback when appropriate.

Take Quiz

Real Codebase Usage

In production code, developers avoid unbounded recursive paths and validate data before traversing it.

Use a guard clause

function renderNode(node) {
  if (!node) {
    return;
  }

  console.log(node.name);
}

A guard clause prevents calls with missing or invalid values from continuing into code that may fail or loop.

Detect circular object references

API data or application state can accidentally contain cycles. A Set can track objects already visited.

function visitNode(node, visited = new Set()) {
  if (!node || visited.has(node)) {
    return;
  }

  visited.add(node);
  console.log(node.name);

  for (const child of node.children ?? []) {
    visitNode(child, visited);
  }
}

Prefer iteration for long lists

function sendRequests(items) {
  for (const item of items) {
    console.log(`Processing ${item.id}`);
  }
}

Yield during expensive browser work

function processInChunks(items, startIndex = 0) {
  const chunkSize = 100;
  const endIndex = Math.min(startIndex + chunkSize, items.length);

  for (let index = startIndex; index < endIndex; index += 1) {
    processItem(items[index]);
  }

  if (endIndex < items.length) {
    setTimeout(() => processInChunks(items, endIndex), 0);
  }
}

Chunking can help avoid timeout warnings, but it does not fix incorrect logic. First ensure that the algorithm has a valid end condition.

Common Mistakes

Forgetting the base case

Broken:

function walk(node) {
  walk(node.child);
}

If node.child eventually becomes null, this code still calls walk(null) and may fail. If it loops back to an earlier node, it can recurse forever.

Better:

function walk(node) {
  if (!node) {
    return;
  }

  walk(node.child);
}

Using recursion for a huge linear list

Broken for sufficiently large input:

function printItems(items, index = 0) {
  if (index === items.length) {
    return;
  }

  console.log(items[index]);
  printItems(items, index + 1);
}

Use a loop for a long flat array:

for (const item of items) {
  console.log(item);
}

Accidental circular references

A parent may reference a child while the child references the parent. Traversal must track visited objects or deliberately avoid following the back-reference.

Treating a timeout as proof of a stack overflow

A timeout can be caused by a while loop, heavy synchronous processing, or a slow algorithm without recursion. Inspect the code rather than assuming both messages mean exactly the same thing.

Catching the error instead of fixing the cause

try {
  repeatForever();
} catch (error) {
  console.error(error);
}

Catching may log the failure, but it does not make the algorithm correct. Add termination conditions, remove cycles, or use iteration.

Comparisons

SituationMain limit reachedTypical causeBetter approach
Maximum call stack size exceededNested function-call depthInfinite recursion or recursion that is too deepAdd a base case, detect cycles, or use a loop
execution exceeded timeoutTime spent blocking the browserInfinite loop or expensive synchronous workFix termination, optimize, or process in chunks
for / while loopNo growing call stack per iterationRepeating a flat taskUse a clear loop condition
RecursionOne call stack entry per nested callTraversing naturally nested dataUse only when depth is controlled or small
PatternBest forCaution
RecursionTrees, nested menus, nested commentsDeep or cyclic data can overflow
LoopArrays and repeated linear workMust update the loop condition correctly
Chunked asynchronous processingLarge browser-side workloadsRequires code that preserves progress between chunks
Take Quiz

Cheat Sheet

// Safe recursion: always define a base case
function recurse(value) {
  if (stopCondition(value)) {
    return;
  }

  recurse(nextValue(value));
}
  • Call stack: Records active function calls.
  • Stack overflow: Too many nested calls; often shown as Maximum call stack size exceeded.
  • Base case: A condition that ends recursion.
  • Direct recursion: a() calls a().
  • Indirect recursion: a() calls b(), which calls a().
  • Circular data: A traversal revisits an earlier object; use a Set of visited objects.
  • Timeout: Script blocks the browser too long; it is not necessarily a stack overflow.
  • Use a loop for large, flat sequences.
  • Use chunking for expensive browser work that must not freeze the UI.
  • Browser stack and timeout limits vary; do not rely on a particular limit.

FAQ

What does “Maximum call stack size exceeded” mean in JavaScript?

It means JavaScript attempted too many nested function calls. The most common cause is recursion that does not stop.

Does a maximum call stack error stop JavaScript completely?

It stops the current synchronous call path by throwing an error. Statements after the failing call are skipped unless the error is caught. Other future browser events may still execute.

Is Safari's “execution exceeded timeout” the same as a stack overflow?

No. A stack overflow is too much call depth; a timeout is too much uninterrupted execution time. Infinite recursion can contribute to either, but infinite loops and slow code can also cause timeouts.

Why does the code fail in Safari but not Chrome?

Browsers can have different stack-depth limits and timeout behavior. Safari may expose an existing recursion or performance problem earlier than another browser.

How do I find the recursive call causing the error?

Use Safari Web Inspector or another browser debugger, enable pause on exceptions, and inspect the call stack. Look for repeated function names or a repeating chain of functions.

Can I increase the JavaScript call stack limit in Safari?

Web pages should not rely on increasing it. Redesign the code with a stopping condition, iteration, cycle detection, or chunked processing.

Does setTimeout fix recursive stack overflow?

It can break a chain into separate event-loop tasks, so calls do not accumulate on one stack. However, it does not fix an infinite process; you still need a valid stopping condition.

Can DWR itself cause this error?

Any library callback flow can expose the issue, but the error indicates JavaScript call behavior. Check application callbacks, event handlers, serialization of circular data, and code that repeatedly triggers a DWR call or callback.

Take Quiz

Mini Project

Description

Build a safe category-tree walker. Category data is commonly nested in menus, product catalogs, and document trees. The walker will print every category while preventing an accidental circular reference from causing infinite recursion.

Goal

Traverse nested categories exactly once, even if the data contains a cycle.

Requirements

  • Create a nested category structure with names and optional child categories.
  • Write a recursive function that prints each category name.
  • Stop safely when a category is missing.
  • Track previously visited category objects.
  • Demonstrate that an accidental circular reference does not crash the script.
Take Quiz

Keep learning

Related questions

View more