Behavioral pattern · Gang of Four · Beginner

Iterator

Provide a way to access the elements of a collection sequentially without exposing its underlying representation.

Also known as — Cursor

Behavioral Beginner Complete

📺 Analogy

A TV remote’s “channel up” button steps you through channels one at a time. You never need to know whether channels are stored in an array, a linked list, or fetched over the network — you just keep pressing “next” until there’s nothing left. That button is an iterator.

The problem

Different collections store their elements differently — a menu in a slice, a filesystem in a tree, query results in a database cursor. If callers loop using each collection’s internal shape, they’re coupled to that shape. Iterator gives every collection the same traversal interface, so the container can change internally without breaking the code that walks it.

Structure

classDiagram
  class Iterator {
    <<interface>>
    +HasNext() bool
    +Next() string
  }
  class MenuIterator {
    -items string[]
    -pos int
    +HasNext() bool
    +Next() string
  }
  Iterator <|.. MenuIterator

Idiomatic Go — the classic form

The classic GoF form is a small Iterator interface with HasNext/Next. (Returning a concrete type beats interface{} so callers skip type assertions.) Edit and Run:

package main

import "fmt"

// Iterator: a uniform way to walk a collection.
type Iterator interface {
	HasNext() bool
	Next() string
}

type MenuIterator struct {
	items []string
	pos   int
}

func (m *MenuIterator) HasNext() bool { return m.pos < len(m.items) }
func (m *MenuIterator) Next() string {
	item := m.items[m.pos]
	m.pos++
	return item
}

func main() {
	it := &MenuIterator{items: []string{"Pizza", "Burger", "Risotto"}}
	for it.HasNext() {
		fmt.Println(it.Next())
	}
}

Idiomatic Go — range-over-func (Go 1.23+)

Modern Go makes custom iteration first-class. A function of type iter.Seq[T] can be ranged over directly — no HasNext/Next, no exposed index:

import "iter"

func Menu() iter.Seq[string] {
	items := []string{"Pizza", "Burger", "Risotto"}
	return func(yield func(string) bool) {
		for _, item := range items {
			if !yield(item) { // stop early if the caller breaks
				return
			}
		}
	}
}

func main() {
	for dish := range Menu() { // looks just like ranging a slice
		fmt.Println(dish)
	}
}

🐹 Go gives you three iterators

For a plain slice or map, just use for range. For custom sequences, prefer range-over-func (iter.Seq). Reach for the classic HasNext/Next interface only when you need an explicit cursor object you can pass around or pause. Channels are a fourth, concurrent option — see Generator.

In the standard library

  • bufio.ScannerScan() / Text() walk tokens without exposing the buffer.
  • sql.RowsNext() / Scan() iterate query results.
  • slices.All, maps.Keys, strings.Lines (Go 1.23+) — return iter.Seq values.

Pitfalls

⚠️ Don't out-engineer a slice

If your data is already a slice, a custom iterator adds indirection for nothing — for range is the idiomatic answer. Build an iterator when the structure is non-trivial (a tree, a paged API, a stream) or when you genuinely need to hide it.

When to use it — and when not

✅ Reach for it when

  • Callers should traverse a collection without knowing whether it's a slice, tree, ring buffer, or DB cursor.
  • You want one uniform traversal API across different container types.
  • You need more than one independent walk over the same data at once.

⛔ Think twice when

  • It's a plain slice or map — a `for range` is simpler than any iterator machinery.
  • You'd be building an iterator framework where the language already gives you one.

Check your understanding

Score: 0 / 3

1. What does the Iterator pattern hide from the caller?

The caller uses HasNext/Next (or `range`) without depending on how the collection stores its elements, so the container can change internally without breaking callers.

2. What is Go's modern, built-in form of Iterator (Go 1.23+)?

Go 1.23 added range-over-func: a function of type iter.Seq[T] can be ranged over directly, making custom iteration first-class without HasNext/Next boilerplate.

3. Which is a standard-library iterator?

bufio.Scanner walks tokens (lines, words) one at a time via Scan()/Text() without revealing the buffer underneath — a textbook iterator. sql.Rows is another.