Question
JavaScript Maximum Call Stack Size Exceeded: Causes, Safari Fixes, and Timeouts
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.
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:
sumTo(3)is called. It needs3 + sumTo(2).sumTo(2)is called. It needs2 + sumTo(1).sumTo(1)is called. It needs1 + sumTo(0).sumTo(0)reaches the base case and returns0.sumTo(1)returns1 + 0, which is1.sumTo(2)returns2 + 1, which is3.sumTo(3)returns3 + 3, which is6.console.log(total)prints6.
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.
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
| Situation | Main limit reached | Typical cause | Better approach |
|---|---|---|---|
Maximum call stack size exceeded | Nested function-call depth | Infinite recursion or recursion that is too deep | Add a base case, detect cycles, or use a loop |
execution exceeded timeout | Time spent blocking the browser | Infinite loop or expensive synchronous work | Fix termination, optimize, or process in chunks |
for / while loop | No growing call stack per iteration | Repeating a flat task | Use a clear loop condition |
| Recursion | One call stack entry per nested call | Traversing naturally nested data | Use only when depth is controlled or small |
| Pattern | Best for | Caution |
|---|---|---|
| Recursion | Trees, nested menus, nested comments | Deep or cyclic data can overflow |
| Loop | Arrays and repeated linear work | Must update the loop condition correctly |
| Chunked asynchronous processing | Large browser-side workloads | Requires code that preserves progress between chunks |
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()callsa(). - Indirect recursion:
a()callsb(), which callsa(). - Circular data: A traversal revisits an earlier object; use a
Setof 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.
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.
Keep learning
Related questions
Add a Favicon to a Static HTML Page
Learn how to add a favicon to static HTML pages, use correct paths and icon formats, and troubleshoot browser caching and local-file issues.
Allow Only Numeric Input (0-9) in HTML Input Using jQuery
Learn how to allow only digits 0-9 in an HTML input using jQuery, with examples, validation tips, common mistakes, and best practices.
Apply CSS !important with jQuery .css()
Learn why jQuery .css() cannot set !important and how to use CSSStyleDeclaration.setProperty() to apply inline important styles safely.