I. Iterator — Behavioral Pattern
Iterator provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation — whether that's a slice, a linked list, a tree, or something else. The collection exposes a CreateIterator() method that returns an object satisfying a common Iterator interface, and the client walks the collection using only HasNext() and Next().
This solves the problem of traversal logic leaking into client code. Without Iterator, the client has to know whether the underlying store is a slice (needs an index), a map (needs a different traversal), or a tree (needs recursion) — and that knowledge gets duplicated everywhere the collection is walked. Iterator moves the traversal state into a small, separate cursor object, so the client's loop is always the same two-method dance, no matter what's actually being iterated.
It's the same shape as Go's own range keyword under the hood, or a database cursor that fetches one row at a time — the caller stays generic, the traversal mechanics stay hidden behind the cursor.
II. Real-world Example
A coffee shop's menu, where a client wants to print every item on the menu without knowing how the menu stores them:
- Menu (Collection) — holds a slice of
MenuItemand exposesAddItem(name, price)plusCreateIterator(), which returns a freshMenuIteratorpositioned before the first item.
- Collection (interface) — declares
CreateIterator() Iterator, the contract any iterable type must implement.
- Iterator (interface) — declares
HasNext() boolandNext() MenuItem, the contract every cursor must implement.
- MenuIterator — the concrete cursor. It keeps a reference to the
Menuand anindex, and walks the items one at a time.
- MenuItem — a simple value type:
NameandPrice.
The client only ever calls menu.CreateIterator(), then loops with HasNext()/Next() — it never touches menu.items directly.

III. Implementation
iterator.go
package iterator
type Iterator interface {
HasNext() bool
Next() MenuItem
}
type Collection interface {
CreateIterator() Iterator
}menu.go
package iterator
type MenuItem struct {
Name string
Price float64
}
type Menu struct {
items []MenuItem
}
func NewMenu() *Menu {
return &Menu{}
}
func (m *Menu) AddItem(name string, price float64) {
m.items = append(m.items, MenuItem{Name: name, Price: price})
}
func (m *Menu) CreateIterator() Iterator {
return &MenuIterator{menu: m}
}menu_iterator.go
package iterator
type MenuIterator struct {
menu *Menu
index int
}
func (i *MenuIterator) HasNext() bool {
return i.index < len(i.menu.items)
}
func (i *MenuIterator) Next() MenuItem {
item := i.menu.items[i.index]
i.index++
return item
}main.go (usage)
menu := iterator.NewMenu()
menu.AddItem("Espresso", 3.00)
menu.AddItem("Latte", 4.50)
menu.AddItem("Croissant", 2.75)
menuIterator := menu.CreateIterator()
for menuIterator.HasNext() {
item := menuIterator.Next()
fmt.Printf("%s: %.2f\n", item.Name, item.Price)
}IV. Explain the example above
iterator.NewMenu()creates an emptyMenu.
- Three
AddItemcalls appendMenuItem{Espresso, 3.00},{Latte, 4.50}, and{Croissant, 2.75}to the internal slice — the client never sees this slice directly.
menu.CreateIterator()returns a*MenuIteratorwithindex = 0, holding a reference back tomenu.
- The
for menuIterator.HasNext()loop repeatedly checksindex < len(menu.items), and eachNext()call returns the item at the current index before advancing it.
- Once
indexreaches the length of the slice,HasNext()returnsfalseand the loop ends.
Running it produces:
*** Example Iterator ***
Espresso: 3.00
Latte: 4.50
Croissant: 2.75
*** End of Iterator ***Notice that the client code never indexes into a slice or checks a length itself — it only calls HasNext() and Next(). If Menu later switched its internal storage to a linked list or a map, MenuIterator would change, but the client's loop would not.
V. Conclusion
Iterator earns its keep when a collection's internal representation is non-trivial — a tree, a graph, a paginated API response — or likely to change, and you want traversal code that doesn't care which. It also matters when several different collection types need to expose the same traversal contract, so client code can loop over any of them identically.
For a single slice that's only ever looped over once, in one place, Go's built-in range is simpler and the extra interface is pure ceremony. Iterator pays off once traversal logic needs to be reused across multiple collection types, or once the collection's storage might change without client code needing to change too.
No pattern is universally superior.
VI. References
- Go Design Patterns (Mario Castro Contreras)




