voltar aos artigosfunctional programming
$cd~/artigos/recursion-javascript-funcao-chama-si-mesma
Functional Programming
série 33 Conceitos JS·parte 28 de 38

Recursion: Quando a Função Olha no Espelho

22 minVanderlei José Alamino

"Para entender recursão, primeiro você deve entender recursão." — provérbio clássico de programação

Recursão é quando uma função chama a si mesma para resolver versões menores do mesmo problema — até chegar num caso simples o suficiente para resolver direto.

snippetJavaScript
function sumTo(n) {  if (n <= 1) return n;  return n + sumTo(n - 1);} sumTo(5);  // 15

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você vai aprender:

  • Caso base e caso recursivo
  • Relação com a call stack
  • Padrões clássicos e casos práticos (DOM, JSON, listas)
  • Memoização, tail recursion e quando preferir loops

Pré-requisitos: Call Stack e Map, Reduce & Filter — recursão e HOFs combinam em travessias elegantes.

recursion-javascript-funcao-chama-si-mesma.meta.jsonjson

// metadados do artigo

{
  "tag": "functional programming",
  "readTime": "22 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 28,
    "total": 38
  }
}
abrir fonte original
recursion-javascript-funcao-chama-si-mesma.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

Analogia: Bonecas Matryoshka

Cada boneca contém uma menor. Você abre até a menor — que não abre mais (caso base). Depois fecha de volta, devolvendo valores pela cadeia.

snippettext
Abrir  = chamada recursiva (problema menor)Menor  = caso base (parar)Fechar = retornos subindo pela stack

Na recursão, cada chamada espera a de cima retornar antes de concluir seu próprio cálculo.

02

seção

As Duas Partes da Recursão

snippetJavaScript
function solve(problem) {  if (problem is simple enough) {    return solution directly;  // CASO BASE  }  return solve(smaller problem);  // CASO RECURSIVO}
  1. Caso base — para a recursão; retorna sem nova chamada.
  2. Caso recursivo — chama a si mesma com input mais simples.
snippetJavaScript
function sumTo(n) {  if (n <= 1) return n;  return n + sumTo(n - 1);} sumTo(5);  // 5 + sumTo(4) + ... + 1 = 15

A pergunta "soma de 1 a 5?" vira "5 + soma de 1 a 4" — até n === 1.

03

seção

Recursão e a Call Stack

Cada chamada empilha um contexto de execução. A função espera a recursiva retornar antes de terminar.

snippettext
sumTo(3) → sumTo(2) → sumTo(1) retorna 1         ← 2+1=3         ← 3+3=6
snippetJavaScript
function sumTo(n) {  console.log(`Chamado sumTo(${n})`);  if (n === 1) {    console.log("  Caso base! Retornando 1");    return 1;  }  const result = n + sumTo(n - 1);  console.log(`  sumTo(${n}) retornando ${result}`);  return result;}

Cada chamada tem seu próprio n — não interferem. Veja Call Stack para o fundamento completo.

04

seção

Padrões Clássicos

Exemplos assumem inteiros não negativos; em produção valide inputs.

Fatorial

snippetJavaScript
function factorial(n) {  if (n <= 1) return 1;  return n * factorial(n - 1);} factorial(5);  // 120
snippettext
factorial(4) = 4 * 3 * 2 * 1 = 24

Fibonacci (ingênuo — lento!)

snippetJavaScript
function fibonacci(n) {  if (n === 0) return 0;  if (n === 1) return 1;  return fibonacci(n - 1) + fibonacci(n - 2);} fibonacci(10);  // 55fibonacci(40);  // >300M chamadas — evite sem memo

Soma 1..n e potência

snippetJavaScript
function power(x, n) {  if (n === 0) return 1;  return x * power(x, n - 1);} function powerFast(x, n) {  if (n === 0) return 1;  if (n % 2 === 0) {    const half = powerFast(x, n / 2);    return half * half;  }  return x * powerFast(x, n - 1);}

Reverter string

snippetJavaScript
function reverse(str) {  if (str.length <= 1) return str;  return str[str.length - 1] + reverse(str.slice(0, -1));} reverse("hello");  // "olleh"
05

seção

Casos de Uso Práticos

Recursão brilha em estruturas aninhadas ou em árvore.

Objetos aninhados

snippetJavaScript
function findAllCounts(obj) {  let total = 0;  for (const key in obj) {    if (key === "count") total += obj[key];    else if (typeof obj[key] === "object" && obj[key] !== null) {      total += findAllCounts(obj[key]);    }  }  return total;}

Achatar arrays

snippetJavaScript
function flatten(arr) {  let result = [];  for (const item of arr) {    if (Array.isArray(item)) result = result.concat(flatten(item));    else result.push(item);  }  return result;} flatten([1, [2, [3, 4]], 5]);  // [1, 2, 3, 4, 5]// Ou arr.flat(Infinity)

Travessia do DOM

snippetJavaScript
function walkDOM(node, callback) {  callback(node);  for (const child of node.children) {    walkDOM(child, callback);  }}

Combina recursão com Higher-Order Functions. Veja também DOM.

Lista encadeada

snippetJavaScript
function sumList(node) {  if (node === null) return 0;  return node.value + sumList(node.next);} function printReverse(node) {  if (node === null) return;  printReverse(node.next);  console.log(node.value);}

Sistema de arquivos

snippetJavaScript
function getTotalSize(node) {  if (node.type === "file") return node.size;  return node.children.reduce((t, c) => t + getTotalSize(c), 0);}
06

seção

Recursão vs Iteração

AspectoRecursãoIteração
LegibilidadeÓtima em árvoresSimples em linear
MemóriaO(n) na stackO(1) típico
PerformanceOverhead de chamadaGeralmente mais rápida
RiscoStack overflow (~10k+)Sem risco de stack
snippetJavaScript
// Recursivofunction factorial(n) {  if (n <= 1) return 1;  return n * factorial(n - 1);} // Iterativofunction factorialIter(n) {  let result = 1;  for (let i = 2; i <= n; i++) result *= i;  return result;}

Use recursão: árvores, DOM, JSON, divide-and-conquer, clareza. Use loops: contagem linear, hot paths, profundidade > ~10.000.

07

seção

Erros Comuns

#1 Caso base ausente

snippetJavaScript
function countdown(n) {  console.log(n);  countdown(n - 1);  // CRASH infinito}// RangeError: Maximum call stack size exceeded function countdownOk(n) {  if (n < 0) return;  console.log(n);  countdownOk(n - 1);}

#2 Caso base inalcançável

snippetJavaScript
// ❌ n===0 nunca alcançado se decremento de 2 a partir de ímparif (n === 0) return;countdown(n - 2); // ✓if (n <= 0) return;

#3 Esquecer return

snippetJavaScript
function sum(n) {  if (n === 1) return 1;  sum(n - 1) + n;  // undefined!} return sum(n - 1) + n;  // ✓

#4 Estado compartilhado mutável

snippetJavaScript
// ❌ count globallet count = 0;function countNodes(node) {  if (!node) return;  count++;  countNodes(node.left);  countNodes(node.right);} // ✓ retorne valoresfunction countNodes(node) {  if (!node) return 0;  return 1 + countNodes(node.left) + countNodes(node.right);}

#5 Fibonacci ingênuo

Subproblemas sobrepostos → O(2^n). Corrija com memoização.

08

seção

Otimizando Recursão

Memoização

snippetJavaScript
function fibonacci(n, memo = {}) {  if (n in memo) return memo[n];  if (n <= 1) return n;  memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);  return memo[n];} fibonacci(100);  // instantâneo — O(n)

Recursão de cauda (tail recursion)

snippetJavaScript
// Não tail: multiplica DEPOIS da chamadafunction factorial(n) {  if (n <= 1) return 1;  return n * factorial(n - 1);} // Tail: última operação é a chamadafunction factorialTail(n, acc = 1) {  if (n <= 1) return acc;  return factorialTail(n - 1, acc * n);}

TCO (Tail Call Optimization) reutilizaria o frame — mas V8/Node e Firefox não implementam TCO na prática; Safari (JavaScriptCore) sim. Não conte com TCO para evitar stack overflow na maioria dos ambientes.

Stack explícita (iterativo)

snippetJavaScript
function sumTreeIterative(root) {  if (!root) return 0;  let sum = 0;  const stack = [root];  while (stack.length) {    const node = stack.pop();    sum += node.value;    if (node.right) stack.push(node.right);    if (node.left) stack.push(node.left);  }  return sum;}

Heap (array) em vez de call stack — profundidade ilimitada na prática.

09

seção

Perguntas Clássicas de Entrevista

10

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • Recursão — função chama a si mesma com problema menor
  • Caso base — obrigatório; para a recursão
  • Caso recursivo — divide e delega
  • Call stack — um frame por chamada; ver Call Stack
  • Árvores/DOM/JSON — recursão brilha aqui
  • Loops — melhor para linear e performance
  • Stack overflow — ~10k+ chamadas; base inalcançável
  • Memoização — Fibonacci O(2^n) → O(n)
  • Tail recursion — TCO raro em V8/Firefox; Safari sim
  • Stack explícita — alternativa iterativa para profundidade

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.