📺 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.Scanner—Scan()/Text()walk tokens without exposing the buffer.sql.Rows—Next()/Scan()iterate query results.slices.All,maps.Keys,strings.Lines(Go 1.23+) — returniter.Seqvalues.
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.
Related patterns
Treat individual objects and compositions of objects uniformly through one common interface.
CONCURRENCY GeneratorProduce a stream of values from a goroutine over a channel, lazily and on demand.
BEHAVIORAL VisitorAdd new operations to a set of object types without modifying those types, by moving each operation into a visitor.
Check your understanding
Score: 0 / 31. 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.