What It Is
The Iterator pattern walks through a collection without exposing how it is stored. The caller asks "is there more?" and "give me the next one" — whether the data lives in a slice, a tree or a database stays private.
For plain slices and maps, Go's range already is your iterator. The pattern matters when the storage is hidden or the walk is complicated.
Example: Walking a Playlist
The playlist keeps its songs private. The iterator offers just HasNext() and Next() — the storage behind them could change completely without breaking any caller.
HasNext and Next
The loop never touches the slice inside the playlist. Swap the slice for a linked list or lazy loading, and this loop still compiles and runs.
Output: Go program and captured output
package main
import "fmt"
type Playlist struct {
songs []string
}
// The iterator walks the collection without exposing the slice.
type Iterator struct {
playlist *Playlist
position int
}
func (p *Playlist) Iterator() *Iterator {
return &Iterator{playlist: p}
}
func (it *Iterator) HasNext() bool {
return it.position < len(it.playlist.songs)
}
func (it *Iterator) Next() string {
song := it.playlist.songs[it.position]
it.position++
return song
}
func main() {
p := &Playlist{songs: []string{"intro", "verse", "chorus"}}
for it := p.Iterator(); it.HasNext(); {
fmt.Println("playing:", it.Next())
}
}
When to Use It
Use it when the collection's insides must stay private, when walking is nontrivial (trees, pages from an API), or when you need several independent walks at once.
Skip it for slices and maps in your own code — range is shorter, faster and known to every reader.
Pros and Cons
Pros: storage stays private and changeable; several walks can run independently; complicated traversal is written once.
Cons: more code than range; a live iterator over a changing collection is a classic source of subtle bugs.