---
title: "Iteradores em Go: range-over-func, iter.Seq e Padrões Práticos"
url: "https://golang.com.br/blog/iteradores-go-range-over-func-iter-seq/"
markdown_url: "https://golang.com.br/blog/iteradores-go-range-over-func-iter-seq.MD"
description: "Iteradores em Go na prática: range-over-func (Go 1.23), o pacote iter, iter.Seq e iter.Seq2, slices.Values/All, maps.All, pull iterators, pipelines preguiçosos, performance e armadilhas. Com exemplos de produção."
date: "2026-07-19"
author: "Golang Brasil"
---

# Iteradores em Go: range-over-func, iter.Seq e Padrões Práticos

Iteradores em Go na prática: range-over-func (Go 1.23), o pacote iter, iter.Seq e iter.Seq2, slices.Values/All, maps.All, pull iterators, pipelines preguiçosos, performance e armadilhas. Com exemplos de produção.


Desde o Go 1.23, a linguagem ganhou uma forma oficial de escrever **iteradores**: a capacidade de usar `for...range` diretamente sobre uma função. O [resumo do Go 1.23](/blog/go-1-23/) menciona a novidade em uma linha; este guia aprofunda como ela funciona de verdade e quando vale a pena usá-la em produção. Se você já domina [generics](/blog/generics-go-constraints-interfaces/) e [concorrência com channels](/blog/channels-go-comunicacao-goroutines-producao/), vai reconhecer os dois pilares dos iteradores: type parameters e o padrão de "empurrar" valores para o chamador.

A motivação é prática. Antes do Go 1.23, percorrer uma estrutura customizada exigia expor um slice interno, implementar `Len()/Get(i)` ou abrir um channel só para iterar — e channels arrastam goroutines e custo. Com `range-over-func`, você escreve uma função que produz valores sob demanda, e o `range` cuida do resto. O resultado é código mais simples, pipelines preguiçosos (lazy) sem alocações intermediárias e composição natural: filtrar, mapear e acumular viram funções de uma linha.

## Como funciona o range-over-func

A regra é direta: quando o `range` recebe uma função, o Go chama essa função passando como argumento outra função, `yield`. Cada chamada `yield(valor)` entrega um valor ao corpo do `for`. Se o chamador executar `break`, `yield` retorna `false` e a função iteradora deve parar. Quando a função retorna, o loop termina.

```go
func UmACinco(yield func(int) bool) {
    for i := 1; i <= 5; i++ {
        if !yield(i) {
            return // chamador deu break
        }
    }
}

func main() {
    for v := range UmACinco {
        fmt.Println(v) // 1 2 3 4 5
    }
}
```

Repare em dois detalhes que confundem iniciantes. Primeiro, `yield` retorna um `bool`: você **precisa** checar e retornar quando ele devolver `false`, senão continua processando depois do chamador ter saído. Segundo, a função iteradora não itera "sozinha": o `range` é quem a executa, então todo o trabalho acontece de forma síncrona, na mesma goroutine, sem canais.

## O pacote `iter`: `Seq` e `Seq2`

O pacote padrão `iter` existe só para nomear duas assinaturas, que servem como "contrato" entre produtores e consumidores de iteradores:

```go
type Seq[V any]    func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
```

`Seq[V]` é a versão de um valor (equivalente a `for v := range`). `Seq2[K, V]` é a versão de par chave-valor (equivalente a `for k, v := range`), usada para mapas e para slices com índice. O exemplo acima fica idiomático assim:

```go
import "iter"

func UmACinco() iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 1; i <= 5; i++ {
            if !yield(i) {
                return
            }
        }
    }
}
```

A função externa retorna a função interna. Isso permite criar iteradores parametrizados (Fibonacci, linhas de um arquivo, registros de um cursor) sem estado global.

## Iterador infinito, consumo controlado

O caso-clássico que mostra a vantagem dos iteradores é gerar uma sequência teoricamente infinita e consumi-la com `break`. Fibonacci:

```go
func Fibonacci() iter.Seq[int] {
    return func(yield func(int) bool) {
        a, b := 0, 1
        for {
            if !yield(a) {
                return
            }
            a, b = b, a+b
        }
    }
}

func main() {
    for n := range Fibonacci() {
        if n > 100 {
            break
        }
        fmt.Println(n) // 0 1 1 2 3 5 8 13 21 34 55 89
    }
}
```

Sem iteradores, isso exigiria um canal com goroutine (e o cuidado de fechá-lo) ou um loop manual com variável de estado. Aqui, `break` aciona o retorno `false` do `yield`, a função para e nada vaza. É o mesmo princípio de laziness que torna [streams e pipelines](/aprenda/golang-para-backend/) eficientes em outras linguagens.

## Coleções nativas: `slices` e `maps`

Você raramente vai escrever `iter.Seq` do zero. O Go já empacota iteradores para as coleções do dia a dia no pacote `slices` e `maps`:

```go
import (
    "maps"
    "slices"
)

func main() {
    nomes := []string{"Ana", "Bruno", "Carla"}

    // só valores
    for n := range slices.Values(nomes) {
        fmt.Println(n)
    }

    // índice + valor (substitui o range clássico quando conveniente)
    for i, n := range slices.All(nomes) {
        fmt.Println(i, n)
    }

    // de trás pra frente
    for i, n := range slices.Backward(nomes) {
        fmt.Println(i, n)
    }

    // mapas com ordem indefinida, mas composável
    idades := map[string]int{"Ana": 30, "Bruno": 25}
    for k, v := range maps.All(idades) {
        fmt.Println(k, v)
    }
}
```

A mágica está em funções de coleta que consomem um iterador e devolvem uma estrutura pronta: `slices.Collect` junta tudo num slice, `slices.Sorted` devolve ordenado, `slices.AppendSeq` acumula num slice existente, e `maps.Collect` / `maps.Insert` populam mapas:

```go
pares := slices.Collect(func(yield func(int) bool) {
    for i := 2; i <= 10; i += 2 {
        if !yield(i) {
            return
        }
    }
})
// pares == []int{2, 4, 6, 8, 10}
```

## Pipelines preguiçosos: filtro e mapa

É aqui que iteradores brilham. Combinando `slices.Values`, um `Filter` e um `Map`, você processa coleções grandes sem alocar slices intermediárias — cada elemento percorre o pipeline de ponta a ponta antes do próximo ser lido:

```go
func Filter[V any](seq iter.Seq[V], pred func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if pred(v) && !yield(v) {
                return
            }
        }
    }
}

func Map[V, W any](seq iter.Seq[V], f func(V) W) iter.Seq[W] {
    return func(yield func(W) bool) {
        for v := range seq {
            if !yield(f(v)) {
                return
            }
        }
    }
}
```

Encadeando:

```go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

paresDobrados := slices.Collect(
    Map(
        Filter(slices.Values(nums), func(n int) bool { return n%2 == 0 }),
        func(n int) int { return n * 2 },
    ),
)
// paresDobrados == []int{4, 8, 12, 16, 20}
```

Sem iteradores, isso seriam dois slices temporários (um do filtro, um do mapa). Com iteradores, o processamento é elemento a elemento e pode ser zero-alocação se as closures forem simples. Para coleções de centenas de milhares de itens, a diferença de memória é grande. Se for medir isso de verdade, leia o [guia de pprof em produção](/blog/pprof-go-producao/).

## Processamento em lotes com `slices.Chunk`

Outro iterador nativo que resolve um problema recorrente é o `slices.Chunk`, que fatia uma coleção em pedaços de tamanho fixo — útil para batch insert, paginação em memória e chamadas em lote:

```go
func ProcessarTudo(ids []int64) {
    for lote := range slices.Chunk(ids, 500) {
        // lote é um []int64 com até 500 elementos
        if err := repositorio.BulkInsert(lote); err != nil {
            return err
        }
    }
}
```

Antes, todo mundo reescrevia essa mesma lógica de "calcula início e fim, fatia com cuidado no limite". Agora é uma linha legível.

## Pull iterators: `iter.Pull` e `iter.Pull2`

Tudo até aqui usa **push iterators**: a função "empurra" valores para o `yield`. Existe o caminho inverso, o **pull iterator**, em que o chamador "puxa" um valor de cada vez com uma função `next()`. O pacote `iter` oferece `iter.Pull` e `iter.Pull2`:

```go
func Zip[A, B any](a iter.Seq[A], b iter.Seq[B]) iter.Seq2[A, B] {
    return func(yield func(A, B) bool) {
        nextA, stopA := iter.Pull(a)
        defer stopA()
        nextB, stopB := iter.Pull(b)
        defer stopB()

        for {
            va, okA := nextA()
            vb, okB := nextB()
            if !okA || !okB {
                return
            }
            if !yield(va, vb) {
                return
            }
        }
    }
}
```

`Zip` é o exemplo canônico: você só consegue intercalar duas sequências puxando um valor de cada uma. Note o `defer stop()` — **obrigatório**. O pull iterator usa uma coroutine interna e, se você não chamar `stop`, vaza. Para o dia a dia, prefira push iterators; reserve `iter.Pull` para casos de intercalação real.

## Armadilhas que custam caro

**Ignorar o retorno de `yield`.** Sempre faça `if !yield(v) { return }`. Se você continuar iterando depois de um `break`, pode processar milhões de itens à toa — e, em iteradores sobre I/O (linhas de arquivo, registros), isso significa ler dados que ninguém vai usar.

**Chamar `yield` depois de a função ter retornado.** Guardar a função `yield` em uma struct e invocá-la mais tarde (de outra goroutine, de um timer) entra em pânico. O contrato é claro: `yield` só é válida durante a execução síncrona do iterador.

**Esquecer `stop()` no pull iterator.** Vaza coroutine. Sempre use `defer stop()` logo após criar o pull iterator.

**Assumir ordem em `maps.All`.** Mapas em Go não têm ordem garantida; `maps.All` reflete isso. Se precisa de ordem, extraia as chaves com `slices.Sorted(maps.Keys(m))` e itere sobre elas.

## Iteradores, channels e callbacks: qual usar?

A escolha depende de onde os dados nascem:

| Origem dos dados | Ferramenta certa |
|---|---|
| Coleção em memória (slice, mapa, árvore) | **Iterador** (`iter.Seq`) |
| Pipeline de transformações síncronas | **Iterador** (lazy, zero alocação) |
| Eventos assíncronos, streaming de rede | **Channel** ou callback |
| Trabalho concorrente com erro agregado | [`errgroup`](/blog/errgroup-go-concorrencia-cancelamento-erros/) |
| Producer/consumer entre goroutines | **Channel** (veja [channels em produção](/blog/channels-go-comunicacao-goroutines-producao/)) |

A regra prática: se tudo acontece na mesma goroutine e os dados já estão prontos (ou podem ser gerados sob demanda síncrona), iterador. Se os dados chegam no tempo deles, de outra goroutine, channel. Iteradores não substituem channels — eles substituem o pattern feio de "expõe um slice interno ou implementa três métodos só pra dar um range".

## Testando iteradores

Iteradores são funções comuns, então [testes de tabela](/blog/testes-tabela-go-guia-table-driven-tests/) funcionam direto. O truque é coletar a saída com `slices.Collect` e comparar:

```go
func TestFibonacci(t *testing.T) {
    tests := []struct {
        nome    string
        limite  int
        quer    []int
    }{
        {"ate 10", 10, []int{0, 1, 1, 2, 3, 5, 8}},
        {"zero", 0, []int{}},
    }
    for _, tt := range tests {
        t.Run(tt.nome, func(t *testing.T) {
            got := []int{}
            for n := range Fibonacci() {
                if n > tt.limite {
                    break
                }
                got = append(got, n)
            }
            if !slices.Equal(got, tt.quer) {
                t.Errorf("got %v, quer %v", got, tt.quer)
            }
        })
    }
}
```

Teste também o caminho de `break` (iterador infinito com limite) e o caso vazio — são os dois lugares onde iteradores costumam quebrar.

## Perguntas frequentes

### Preco do Go 1.23 em diante para usar iteradores?

Sim, range-over-func e o pacote `iter` são estáveis desde o Go 1.23 (agosto de 2024). Qualquer projeto em Go 1.23+ já tem o recurso disponível na biblioteca padrão, sem dependências externas.

### Iteradores deixam o código mais lento?

Não — pelo contrário, costumam ser mais rápidos e alocam menos que abordagens baseadas em slice temporário ou channel, porque o compilador inlina a função `yield` e não há goroutine envolvida nos push iterators. Como sempre, meça com `go test -bench` antes de otimizar; não troque um `for` simples por um iterador só por estética.

### Posso converter um iterador de volta em slice ou mapa?

Pode. Use `slices.Collect(seq)` para um slice, `slices.Sorted(seq)` para um slice ordenado, `slices.AppendSeq(dst, seq)` para acumular e `maps.Collect(seq2)` ou `maps.Insert(m, seq2)` para montar um mapa. É assim que você "materializa" um pipeline preguiçoso quando precisa do resultado final.

### Como interrompo um iterador infinito?

Com `break` no `for`, exatamente como em um loop comum. O `break` faz o `yield` retornar `false`, e a função iteradora é responsável por checar esse retorno e parar. Por isso a regra de sempre escrever `if !yield(v) { return }`.

### Existe iterador reverso?

Sim. `slices.Backward(s)` retorna um `iter.Seq2[int, T]` que percorre do fim para o início. Para estruturas customizadas, você escreve a ordem que quiser dentro da função iteradora — ela só precisa chamar `yield` na sequência desejada.

---

Iteradores não são açúcar sintático: são a forma idiomática, desde o Go 1.23, de expor sequências lazy e composáveis em Go sem recorrer a channels ou slices intermediários. Comece substituindo aquele método `Items() []T` que devolvia uma cópia por um `iter.Seq[T]`, adote `slices.Values`/`maps.All` no lugar de `range` direto quando precisar compor, e guarde `iter.Pull` só para intercalar sequências. Para se aprofundar na base teórica, leia o [guia de generics](/blog/generics-go-constraints-interfaces/), que explica os type parameters que tornam `Seq[V]` possível, e o [guia de concorrência](/aprenda/concorrencia-go/) para entender a fronteira exata entre iterador síncrono e channel concorrente.
