ComportamentalIniciante6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Iterator

Oferece uma forma de percorrer os elementos de uma coleção sequencialmente sem expor como ela é construída por dentro — e permite mais de uma travessia simultânea.

Em 10 segundos

A travessia vira um objeto com cursor próprio. O cliente pede 'o próximo' e recebe, sem saber se atrás há um array, uma árvore ou uma API paginada — e dois percursos simultâneos não se atrapalham porque cada um tem seu iterador.

Antes de ter nome

CLU, a linguagem de Barbara Liskov (1975), já tinha iteradores na própria sintaxe — a mesma Liskov do L de SOLID.

  • #travessia
  • #colecao
  • #lazy
  • #gof
Analogia

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.

01Conceito

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.

02Conceito

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.

Como se encaixa
  1. for...of
  2. pede o próximoIterador
  3. avança o cursor internoÁrvore
  4. devolve e pausaPróximo valor
O cursor vive no iterador, não na árvore — por isso dois laços simultâneos sobre a mesma coleção não se atrapalham, cada um com sua posição.
Sem o padrão × com o padrão

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.

O ganho é a fronteira: depois de existir um iterador, a estrutura interna da coleção volta a ser assunto dela — que é onde deveria estar desde o começo.
Passo a passoO fluxo, passo a passo
  1. 1

    Expor o protocolo

    A coleção fabrica um iterador novo (ex.: Symbol.iterator / __iter__).

  2. 2

    Cursor no iterador

    A posição mora no iterador — nunca na coleção — para permitir travessias paralelas.

  3. 3

    Pedir o próximo

    O cliente só consome 'próximo' / for-of, sem conhecer a estrutura interna.

  4. 4

    Produzir sob demanda

    Com geradores, cada yield pausa e devolve o controle sem materializar a sequência inteira.

03Anatomia

Navegue pelas camadas

Camada 1/3Cliente

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.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

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]
06No mundo real

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).

08Cuidado

Armadilhas

  1. 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.

  2. 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.

  3. 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 via try/finally no gerador.

09Decisão

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