🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Como lidar com o tamanho máximo da pilha de chamadas excedido em JavaScript

⏱️6 min read  ·  1,143 words

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.

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.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা