O marcador de página
O marcador guarda onde você parou; o livro não. É por isso que duas pessoas podem ler o mesmo exemplar em ritmos diferentes, cada uma com seu marcador. Se a posição estivesse anotada no livro, a segunda leitora apagaria o progresso da primeira. O marcador também funciona igual em qualquer livro, sem saber nada do assunto.
O problema
Cada estrutura se percorre de um jeito. Se o cliente precisa saber qual delas está usando, trocar a estrutura vira uma refatoração em todo lugar que a percorre.
Guardar a posição dentro da própria coleção parece prático e quebra na primeira travessia aninhada: o laço de fora e o de dentro compartilham o mesmo cursor.
Este é, provavelmente, o padrão do GoF mais absorvido pelas linguagens. for...of com Symbol.iterator, __iter__ em Python, IEnumerable em C#, Iterable em Java — todos são o Iterator embutido no idioma. Por isso o padrão hoje raramente se implementa do zero; ele se implementa aderindo ao protocolo da linguagem, que é o que o exemplo faz com geradores.
Aderir ao protocolo é o que rende o benefício maior: sua coleção passa a funcionar com desestruturação, spread, for...of, e com toda a biblioteca que fala essa língua. Um iterador caseiro com proximo() e temProximo() funciona, mas fica fora do ecossistema.
Geradores resolvem a parte mais chata do padrão. Um iterador de árvore escrito à mão precisa manter uma pilha explícita para lembrar onde estava; com yield, a própria linguagem congela a posição da recursão. O que era uma classe com estado vira uma função com pausas.
Travessia preguiçosa
Um iterador não precisa ter todos os itens prontos. Produzir sob demanda permite percorrer sequências grandes, caras ou infinitas com memória constante.
Ler um arquivo de dez gigabytes linha a linha, paginar uma API buscando a próxima página só quando a atual acabar, gerar números primos indefinidamente — todos são iteradores preguiçosos. A memória fica proporcional ao item atual, não à sequência.
Isso muda a forma de compor: filter e map preguiçosos não criam listas intermediárias, então encadear cinco transformações sobre um milhão de itens continua custando um item por vez.
O preço é que a preguiça esconde trabalho. Um for que parece percorrer uma lista pode estar disparando uma chamada HTTP por página, e o custo real aparece no meio do laço. Iteradores preguiçosos sobre recursos externos precisam deixar isso explícito no nome e tratar falha no meio da travessia.
- for...of
- pede o próximoIterador
- avança o cursor internoÁrvore
- devolve e pausaPróximo valor
Com Iterator: a travessia é um objeto
- o cliente só pede o próximo
- trocar a estrutura interna não afeta ninguém
- cada laço tem seu cursor independente
- a mesma coleção oferece várias ordens de travessia
O custo é uma indireção por item e, se você aderir ao protocolo da linguagem, praticamente nenhuma cerimônia — mas a coleção não pode mais ser modificada durante o percurso.
- 1
Expor o protocolo
A coleção fabrica um iterador novo (ex.: Symbol.iterator / __iter__).
- 2
Cursor no iterador
A posição mora no iterador — nunca na coleção — para permitir travessias paralelas.
- 3
Pedir o próximo
O cliente só consome 'próximo' / for-of, sem conhecer a estrutura interna.
- 4
Produzir sob demanda
Com geradores, cada yield pausa e devolve o controle sem materializar a sequência inteira.
Navegue pelas camadas
Camada 1/3Cliente
use ← →Camada 1
Cliente
consome a sequência
Usa o protocolo de iteração da linguagem e depende só dele. Não guarda índices nem assume que pode voltar — se precisar de acesso aleatório, iterador é a ferramenta errada.
No código
for (const v of arvore) console.log(v);Se violar: cliente que faz `colecao.nos[i]` para 'otimizar' rompeu a fronteira e volta a depender da estrutura.
Estrutura
Código
// A colecao esconde a estrutura (aqui, uma arvore binaria)
class No {
constructor(
readonly valor: number,
readonly esq?: No,
readonly dir?: No,
) {}
}
class Arvore {
constructor(private raiz?: No) {}
// Iterador em ordem, sem materializar a arvore inteira
*[Symbol.iterator](): Generator<number> {
function* percorrer(n?: No): Generator<number> {
if (!n) return;
yield* percorrer(n.esq);
yield n.valor; // pausa aqui e devolve o controle
yield* percorrer(n.dir);
}
yield* percorrer(this.raiz);
}
}
const arvore = new Arvore(
new No(5, new No(3, new No(1)), new No(8, new No(7))),
);
// O cliente nao sabe que ha uma arvore embaixo
for (const v of arvore) console.log(v); // 1 3 5 7 8
// E ganha de graca tudo que fala a linguagem de iteracao
console.log([...arvore].filter((v) => v > 4)); // [5, 7, 8]class No:
def __init__(self, valor, esq=None, dir=None):
self.valor, self.esq, self.dir = valor, esq, dir
class Arvore:
def __init__(self, raiz=None):
self._raiz = raiz
# Iterador em ordem, sem materializar a arvore inteira
def __iter__(self):
def percorrer(n):
if n is None:
return
yield from percorrer(n.esq)
yield n.valor # pausa aqui e devolve o controle
yield from percorrer(n.dir)
yield from percorrer(self._raiz)
arvore = Arvore(No(5, No(3, No(1)), No(8, No(7))))
# O cliente nao sabe que ha uma arvore embaixo
for v in arvore:
print(v) # 1 3 5 7 8
# E ganha de graca tudo que fala a linguagem de iteracao
print([v for v in arvore if v > 4]) # [5, 7, 8]Casos de uso
Paginação transparente de API
Cenário
Um cliente de API precisa percorrer cem mil registros que o servidor devolve em páginas de cem, e nenhum consumidor deveria escrever o laço de paginação.
Como o padrão entra
O SDK expõe uma coleção iterável que busca a próxima página quando a atual se esgota. Quem consome escreve um for simples e nunca vê um token de paginação.
Trade-off: A travessia esconde chamadas de rede: um laço aparentemente inocente pode fazer mil requisições, e uma falha no meio deixa o consumidor com metade dos dados sem sinal claro de que faltou algo.
Leitura de arquivo grande
Cenário
Um job precisa processar um CSV de vários gigabytes numa máquina com pouca memória.
Como o padrão entra
Um iterador entrega uma linha por vez, lendo do disco em blocos. O processamento roda com memória constante, independentemente do tamanho do arquivo.
Trade-off: Só serve para uma passada: operações que precisam olhar o arquivo duas vezes (como calcular média e depois desvio) exigem reabrir e percorrer de novo, ou acumular estado à parte.
Travessias múltiplas de uma árvore de categorias
Cenário
Um catálogo precisa listar categorias em ordem alfabética para o menu, por profundidade para o breadcrumb e por nível para o sitemap.
Como o padrão entra
A mesma árvore expõe três iteradores diferentes. A estrutura é escrita uma vez; as ordens de leitura são objetos independentes que podem ser testados isoladamente.
Trade-off: Cada travessia nova é código novo com sua própria chance de bug sutil (um nó visitado duas vezes, um ramo esquecido) — e esses erros costumam passar por testes de árvores pequenas.
O cursor grudado na coleção
Compila, roda, passa no code review. E quebra depois.
A 'iteração' existe, mas a posição vive na própria coleção. Dois laços brigam pelo mesmo cursor; aninhar percursos quebra sem aviso claro.
class Lista {
private itens: number[];
private posicao = 0; // <- cursor NA colecao
constructor(itens: number[]) {
this.itens = itens;
}
proximo(): number | undefined {
return this.itens[this.posicao++];
}
reset() { this.posicao = 0; }
}
const lista = new Lista([1, 2, 3, 4]);
for (let a = lista.proximo(); a !== undefined; a = lista.proximo()) {
lista.reset(); // laço interno rouba o cursor do de fora
const b = lista.proximo();
}
// Dois percursos simultaneos sao impossiveis: ha um cursor so.O que quebra
- Com laços aninhadosO laço interno reseta ou avança o cursor do externo — itens pulados ou repetidos.
- Em paraleloDuas leituras simultâneas corrompem a posição uma da outra.
- Na APIQuem consome precisa lembrar de reset() — estado escondido vaza para o cliente.
A saída: Cada travessia cria um iterador com cursor próprio. A coleção só fabrica; não guarda posicaoAtual. Prefira o protocolo da linguagem (for...of / geradores).
Armadilhas
Modificar a coleção durante o percurso
Remover ou inserir itens enquanto se itera é a fonte clássica de comportamento indefinido: itens pulados, visitados duas vezes ou exceção de modificação concorrente. A saída é coletar as mudanças durante a travessia e aplicá-las depois, ou iterar sobre uma cópia quando a coleção for pequena.
Iterador de uso único tratado como coleção
Um gerador se esgota: depois do primeiro
for, o segundo não vê nada. Quando isso é devolvido de uma função com nome de coleção, o segundo consumidor recebe uma lista vazia sem erro nenhum. Ou o nome deixa a preguiça explícita, ou a função devolve algo reiterável.Preguiça sobre recurso que fecha
Um iterador que lê de um arquivo ou conexão mantém o recurso aberto enquanto não termina. Se o consumidor abandona o laço no meio com um
break, o recurso pode vazar. Iteradores sobre recursos externos precisam de encerramento garantido — o que a linguagem oferece viatry/finallyno gerador.
Quando usar × quando evitar
Use quando
3- A estrutura interna da coleção não deve vazar para quem percorre.
- Você quer oferecer várias formas de travessia sobre a mesma estrutura.
- A sequência é grande, cara ou infinita e precisa ser produzida sob demanda.
Evite quando
3- É um array simples e a linguagem já resolve.
- O acesso é aleatório por índice, não sequencial.
- A coleção precisa ser modificada durante a travessia.
Testar o que ficou
3 armadilhas de Iterator viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
for...of e Symbol.iteratorO protocolo de iteração é o padrão elevado a contrato da linguagem.Generatorsfunction*produz valores sob demanda, mantendo a posição entre chamadas — iterador com estado.Cursores de bancoPercorrer um resultado grande sem carregar tudo na memória de uma vez.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Percorre sem expor a estrutura.
for (const item of colecao) { /* ... */ }O que ele cobra
1 salto a mais entre a chamada e o efeito.
- Uma classe de iteração a manter junto com a coleção que ela percorre
- Iterar enquanto a coleção muda pode invalidar o iterador de formas sutis
Não vale se: a linguagem já dá iteração pronta sobre a estrutura — reimplementar o protocolo é reinventar o for-of.
Onde isto aparece
Continue por aqui
- CompositeCompõe objetos em árvores e faz o cliente tratar um item isolado e um grupo inteiro exatamente do mesmo jeito, através de uma interface comum.
- VisitorSepara operações da estrutura de objetos sobre a qual elas atuam, permitindo adicionar comportamentos novos sem modificar as classes existentes — ao custo de congelar o conjunto de tipos.
- StrategyEncapsula uma família de algoritmos intercambiáveis por trás de uma interface comum, deixando o cliente trocar a estratégia em tempo de execução.