A recursão é uma técnica de programação onde uma função chama a si mesma para resolver um problema dividindo-o em subproblemas menores e semelhantes. Em Python, a recursão é uma ferramenta elegante e poderosa, especialmente útil para problemas que podem ser decompostos naturalmente em versões menores de si mesmos.

Se você está começando agora, é importante primeiro dominar os fundamentos das funções em Python, pois a recursão depende diretamente do conceito de funções e seus parâmetros. Neste guia completo, você vai entender como a recursão funciona, quando usá-la e como evitar armadilhas comuns como estouro de pilha.

O Que é Recursão?

Recursão ocorre quando uma função define seu comportamento em termos de si mesma. Imagine um espelho refletindo outro espelho — a imagem se repete infinitamente, a menos que haja uma condição de parada. Em computação, toda função recursiva precisa de dois elementos fundamentais:

  • Caso base: a condição que interrompe a recursão
  • Caso recursivo: a chamada da função para uma versão menor do problema

Um exemplo clássico e simples é o cálculo do fatorial de um número. O fatorial de n (representado como n!) é o produto de todos os inteiros positivos menores ou iguais a n. Matematicamente, n! = n × (n-1)!, com 0! = 1. Essa definição já é naturalmente recursiva.

def fatorial(n):
    if n <= 1:
        return 1
    return n * fatorial(n - 1)

print(fatorial(5)) # 120

Quando chamamos fatorial(5), a função se chama sucessivamente com fatorial(4), fatorial(3), fatorial(2), fatorial(1) — que retorna 1 (caso base). Então cada chamada retorna multiplicando o resultado, formando: 1 × 2 × 3 × 4 × 5 = 120.

Como a Pilha de Chamadas Funciona

Cada chamada recursiva é empilhada na call stack (pilha de chamadas) do Python. A cada nova chamada, um novo quadro (frame) é adicionado ao topo da pilha. Quando o caso base é atingido, os quadros começam a ser desempilhados do topo para baixo, retornando os resultados.

Visualizando a execução de fatorial(5):

fatorial(1) → retorna 1
fatorial(2) → 2 * 1 = 2
fatorial(3) → 3 * 2 = 6
fatorial(4) → 4 * 6 = 24
fatorial(5) → 5 * 24 = 120

Cada nível da pilha consome memória. Para recursões muito profundas, o Python lança um RecursionError quando o limite de recursão é excedido. Por padrão, esse limite é 1000 chamadas, mas pode ser ajustado com sys.setrecursionlimit().

import sys
print(sys.getrecursionlimit())  # 1000
sys.setrecursionlimit(3000)     # aumenta para 3000

Segundo a documentação oficial do Python, modificar o limite de recursão deve ser feito com cautela, pois valores muito altos podem causar crash no interpretador devido ao estouro da pilha C.

Caso Base e Caso Recursivo

Todo problema resolvido com recursão deve ter ao menos um caso base bem definido. Sem ele, a função se chamaria infinitamente até causar um estouro de pilha. O caso base é a menor instância do problema, que pode ser resolvida diretamente sem recorrer à recursão.

def contagem_regressiva(n):
    if n == 0:          # caso base
        print("Fogo!")
        return
    print(n)            # ação
    contagem_regressiva(n - 1)  # caso recursivo

contagem_regressiva(5)

No exemplo acima, quando n chega a 0, a recursão para. Cada chamada reduz n em 1, garantindo que o caso base será eventualmente alcançado, desde que n seja um número não negativo.

Fibonacci com Recursão

A sequência de Fibonacci é um dos exemplos mais usados para ensinar recursão. Nela, cada termo é a soma dos dois anteriores, com F(0) = 0 e F(1) = 1.

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10)) # 55

Embora elegante, essa implementação tem complexidade exponencial O(2ⁿ). Para n = 40, ela faz milhões de chamadas recursivas. É um exemplo clássico de onde a recursão ingênua é ineficiente. Para melhorar, podemos usar memoização:

from functools import lru_cache

@lru_cache(maxsize=None) def fibonacci_otimizado(n): if n <= 1: return n return fibonacci_otimizado(n - 1) + fibonacci_otimizado(n - 2)

print(fibonacci_otimizado(100)) # 354224848179261915075

O decorador @lru_cache armazena resultados já calculados, reduzindo a complexidade para O(n). A Real Python tem um excelente tutorial aprofundado sobre recursão e memoização em Python.

Recursão vs Iteração

Toda função recursiva pode ser reescrita como uma função iterativa (usando loops), e vice-versa. A escolha entre uma abordagem e outra depende de vários fatores:

Característica Recursão Iteração
Legibilidade Mais intuitiva para problemas recursivos por natureza Pode ser mais difícil de entender em problemas complexos
Memória Consome mais memória (pilha de chamadas) Consome menos memória
Performance Pode ser mais lenta (overhead de chamadas de função) Geralmente mais rápida
Risco de erro RecursionError se muito profunda Loop infinito se mal escrita

Para problemas como atravessar árvores, processar estruturas aninhadas ou implementar algoritmos de divisão e conquista, a recursão oferece uma solução mais natural e legível. Segundo a GeeksforGeeks, a recursão é particularmente útil para problemas que podem ser definidos em termos de si mesmos.

Recursão de Cauda (Tail Recursion)

Recursão de cauda é uma forma especial de recursão onde a chamada recursiva é a última operação executada pela função. Em linguagens funcionais, isso permite otimizações onde o compilador reusa o mesmo quadro de pilha, evitando o crescimento da call stack. Infelizmente, o Python não implementa otimização de recursão de cauda.

# Recursão de cauda (não otimizada em Python)
def fatorial_cauda(n, acumulador=1):
    if n <= 1:
        return acumulador
    return fatorial_cauda(n - 1, n * acumulador)

O criador do Python, Guido van Rossum, explicou em um artigo que a otimização de recursão de cauda não foi implementada porque prejudicaria a pilha de depuração (stack traces menos informativos). Portanto, em Python, prefira iteração para problemas que exigiriam recursão muito profunda. O Programiz tem exemplos práticos que ilustram bem essa limitação.

Limites da Recursão em Python

O Python define um limite máximo de profundidade para recursão, evitando que um programa consuma toda a memória disponível. Esse limite é controlado pela função sys.getrecursionlimit().

import sys

Verificando o limite atual

print(f"Limite atual: {sys.getrecursionlimit()}")

Aumentando o limite (com cuidado!)

sys.setrecursionlimit(5000)

Verificando se um valor pode causar estouro

def verifica_profundidade(n): try: return verifica_profundidade(n + 1) except RecursionError: return n

print(f"Profundidade máxima alcançável: {verifica_profundidade(0)}")

O FAQ oficial do Python aborda questões relacionadas a parâmetros de funções e recursão, explicando as nuances do comportamento da linguagem.

Problemas Práticos com Recursão

Busca Binária Recursiva

A busca binária é um algoritmo de divisão e conquista que encontra um elemento em uma lista ordenada em tempo O(log n). Sua implementação recursiva é elegante e direta:

def busca_binaria(lista, alvo, esquerda, direita):
    if esquerda > direita:
        return -1  # caso base: elemento não encontrado
meio = (esquerda + direita) // 2

if lista[meio] == alvo:
    return meio  # caso base: elemento encontrado
elif lista[meio] &gt; alvo:
    return busca_binaria(lista, alvo, esquerda, meio - 1)
else:
    return busca_binaria(lista, alvo, meio + 1, direita)

numeros = [1, 3, 5, 7, 9, 11, 13, 15] print(busca_binaria(numeros, 7, 0, len(numeros) - 1)) # 3

Merge Sort Recursivo

O Merge Sort é outro algoritmo clássico de divisão e conquista que utiliza recursão para ordenar elementos com complexidade O(n log n):

def merge_sort(lista):
    if len(lista) <= 1:
        return lista  # caso base
meio = len(lista) // 2
esquerda = merge_sort(lista[:meio])
direita = merge_sort(lista[meio:])

return merge(esquerda, direita)

def merge(esquerda, direita): resultado = [] i = j = 0

while i &lt; len(esquerda) and j &lt; len(direita):
    if esquerda[i] &lt;= direita[j]:
        resultado.append(esquerda[i])
        i += 1
    else:
        resultado.append(direita[j])
        j += 1

resultado.extend(esquerda[i:])
resultado.extend(direita[j:])
return resultado

print(merge_sort([38, 27, 43, 3, 9, 82, 10])) # [3, 9, 10, 27, 38, 43, 82]

A FreeCodeCamp possui um tutorial completo sobre recursão em Python com mais exemplos práticos como esses.

Torre de Hanoi

A Torre de Hanoi é um quebra-cabeça matemático que se resolve de forma quase trivial com recursão, mas é complexo iterativamente:

def torre_hanoi(n, origem, destino, auxiliar):
    if n == 1:
        print(f"Mover disco 1 de {origem} para {destino}")
        return
    torre_hanoi(n - 1, origem, auxiliar, destino)
    print(f"Mover disco {n} de {origem} para {destino}")
    torre_hanoi(n - 1, auxiliar, destino, origem)

torre_hanoi(3, "A", "C", "B")

Para 3 discos, o algoritmo executa 7 movimentos. Para n discos, são necessários 2ⁿ - 1 movimentos. A solução recursiva espelha diretamente a definição matemática do problema, mostrando o poder da recursão.

Quando Usar Recursão em Python

A recursão é mais adequada quando:

  • O problema pode ser decomposto naturalmente em subproblemas menores e similares
  • A profundidade da recursão é previsível e limitada (dezenas, não milhares de níveis)
  • A versão iterativa seria significativamente mais complexa
  • Você está processando estruturas de dados recursivas (árvores, grafos, JSON aninhado)

A Wikipedia oferece uma visão abrangente da recursão na ciência da computação, incluindo suas aplicações em algoritmos e estruturas de dados.

Evite recursão quando:

  • A profundidade pode ser grande e imprevisível
  • A performance é crítica e há overhead de chamadas de função
  • Uma solução iterativa simples existe

Boas Práticas com Recursão

  1. Sempre defina um caso base claro — sem ele, sua recursão nunca termina
  2. Garanta que o caso base será alcançado — cada chamada recursiva deve aproximar-se do caso base
  3. Use memoização@lru_cache ou @cache (Python 3.9+) para evitar recálculos
  4. Prefira iteração para problemas profundos — mais de 500 níveis de recursão podem ser arriscados
  5. Documente a lógica recursiva — a recursão pode ser difícil de ler sem comentários adequados
  6. Teste com valores pequenos primeiro — verifique o caso base, depois aumente gradualmente
  7. Considere alternativas — para problemas como travessia de árvores, iteradores podem ser mais eficientes

Ferramentas para Depurar Recursão

Depurar funções recursivas pode ser desafiador. Algumas técnicas úteis incluem:

import sys
from functools import wraps

def debug_recursao(func): """Decorator que mostra a profundidade da recursão""" nivel = [0]

@wraps(func)
def wrapper(*args, **kwargs):
    indent = "  " * nivel[0]
    print(f"{indent}→ {func.__name__}({args})")
    nivel[0] += 1
    try:
        resultado = func(*args, **kwargs)
        print(f"{indent}← {func.__name__}({args}) = {resultado}")
        return resultado
    finally:
        nivel[0] -= 1

return wrapper

@debug_recursao def fatorial(n): if n <= 1: return 1 return n * fatorial(n - 1)

fatorial(4)

O W3Schools oferece uma introdução simples e direta à recursão em Python, ideal para quem está começando.

Conclusão

A recursão é uma ferramenta indispensável no arsenal de qualquer programador Python. Embora não seja a solução ideal para todos os problemas, dominar a recursão permite escrever código mais elegante e expressivo para uma ampla gama de problemas, especialmente aqueles envolvendo estruturas de dados recursivas e algoritmos de divisão e conquista.

Lembre-se de que a prática leva à perfeição. Comece com problemas simples como fatorial e Fibonacci, depois avance para algoritmos mais complexos como busca binária, Merge Sort e Torre de Hanoi. Com o tempo, você desenvolverá a intuição necessária para identificar quando a recursão é a ferramenta certa para o trabalho.

Para continuar aprendendo, explore outros tópicos fundamentais como listas em Python, que são frequentemente usadas em conjunto com algoritmos recursivos, e aprofunde-se em estruturas de dados como árvores e grafos, onde a recursão brilha. A documentação oficial do Python é um recurso indispensável para qualquer desenvolvedor.