O erroRangeError: Tamanho máximo da pilha de chamadas excedido (também chamado de “estouro de pilha” em ambientes sem navegador) significa que uma função se chamou (direta ou indiretamente) tantas vezes que o JavaScript ficou sem espaço na pilha de chamadas. Aqui estão todas as causas e soluções.
📋 Table of Contents
- Por que isso acontece
- Causa 1: manipulador de eventos recursivos infinitos
- Causa 2: Referências de objetos circulares em JSON.stringify
- Causa 3: Clonagem profunda de objetos com recursão
- Causa 4: Chamar acidentalmente uma função em vez de referenciá-la
- Correção: converter recursão em iteração (trampolim)
- Depurando estouros de pilha
- Perguntas Frequentes
- Conclusão
Por que isso acontece
Cada chamada de função adiciona um quadro à pilha de chamadas. Os mecanismos JavaScript têm um tamanho de pilha limitado (~10.000-15.000 frames dependendo da versão do navegador/nó). Quando as funções recursivas não possuem um caso base adequado ou quando duas funções se chamam indefinidamente, a pilha é preenchida e lança 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);
}
Causa 1: manipulador de eventos recursivos infinitos
// 🐛 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
Causa 2: Referências de objetos circulares em 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
Causa 3: Clonagem profunda de objetos com recursão
// 🐛 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;
}
Causa 4: Chamar acidentalmente uma função em vez de referenciá-la
// 🐛 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');
}
}
Correção: converter recursão em iteração (trampolim)
// 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
Depurando estouros de pilha
// 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);
}
}
Perguntas Frequentes
P: Qual é a profundidade máxima da pilha de chamadas em JavaScript?
R: ~10.000-15.000 quadros no Chrome/Node.js; ~5.000-10.000 no Firefox; varia de acordo com a versão do motor e o tamanho do chassi. Os números exatos mudam entre as versões.
P: Posso aumentar o tamanho da pilha de chamadas?
R: Em Node.js:node --stack-size=65536 app.js (tamanho em KB). Em navegadores: não é possível. Melhor solução: corrija a recursão em vez de aumentar o limite.
P: Qual é a diferença entre estouro de pilha e falta de memória?
R: Estouro de pilha (RangeError) = muitos quadros de chamada de função. Pilha sem memória (FATAL ERROR) = muitos objetos alocados. O estouro de pilha ocorre devido à recursão profunda; erros de heap são decorrentes da alocação de muitos dados.
P: Como posso clonar profundamente um objeto com segurança em 2026?
R: UsestructuredClone(obj) – integrado em todos os navegadores modernos e Node.js 17+. Ele lida corretamente com referências circulares, objetos Date, Maps, Sets e ArrayBuffers. Use JSON.parse(JSON.stringify(obj)) apenas para objetos serializáveis JSON simples sem referências circulares.
P: A otimização da chamada final está disponível em JavaScript?
R: Teoricamente sim (a especificação ES6 inclui o TCO), mas apenas o Safari o implementa totalmente em 2026. V8 (Chrome/Node.js) removeu o TCO. Não confie no TCO para segurança da pilha; em vez disso, use soluções iterativas ou trampolim.
Conclusão
Os erros de tamanho máximo da pilha de chamadas excedidos vêm de recursão infinita ou extremamente profunda. A correção depende da causa:adicione um caso base para interromper a recursão, converta a recursão em iteração para entradas grandes, use StructureClone para cópias profundas e observe referências de objetos circulares. O rastreamento de pilha sempre mostra a linha repetida que causa a recursão infinita – leia-a primeiro e a correção geralmente será óbvia.
🔗 Share this article
✍️ Leave a Comment