1. The Problem
Problem: Traversing a Collection Without Knowing Its Structure
Software frequently needs to traverse collections: lists, trees, graphs, custom containers, streams.
Naive approach: expose internals and loop directly.
for i in range(len(collection.items)):
process(collection.items[i])
Problems:
- Client code depends on internal representation
- Changing the data structure breaks callers
- Different traversal orders require branching
- Multiple traversals duplicate logic
- Violates encapsulation and Single Responsibility
We want to iterate without knowing:
- How data is stored
- Whether it’s a list, tree, or graph
- How traversal is implemented
2. The Iterator Pattern: Decouple Traversal from Structure
The Iterator Pattern separates how data is stored from how it is traversed.
Core idea:
- Define a standard way to traverse elements
- Encapsulate traversal logic
- Hide collection internals
- Support multiple traversal strategies
- Keep client code unchanged
A system using Iterator has two parts:
- Iterator — defines traversal operations
- Aggregate (Collection) — creates iterators
The client only talks to the iterator, not the collection internals.
You get:
- Encapsulation preserved
- Multiple traversal strategies
- Uniform iteration interface
- Clean separation of concerns
Use Iterator when:
- You want to hide collection internals
- You need multiple ways to traverse the same data
- Traversal logic is non-trivial
- You want consistent iteration across different structures
- You want to avoid leaking data structure details
3. Implementation: Iterator + Aggregate in Python
Iterator interface
from abc import ABC, abstractmethod
class Iterator(ABC):
@abstractmethod
def has_next(self) -> bool:
pass
@abstractmethod
def next(self):
pass
Aggregate interface
class Aggregate(ABC):
@abstractmethod
def create_iterator(self) -> Iterator:
pass
Concrete collection
class NumberCollection(Aggregate):
def __init__(self, numbers):
self._numbers = numbers
def create_iterator(self) -> Iterator:
return NumberIterator(self._numbers)
Concrete iterator
class NumberIterator(Iterator):
def __init__(self, numbers):
self._numbers = numbers
self._index = 0
def has_next(self) -> bool:
return self._index < len(self._numbers)
def next(self):
value = self._numbers[self._index]
self._index += 1
return value
Client code (structure-agnostic)
collection = NumberCollection([1, 2, 3, 4])
iterator = collection.create_iterator()
while iterator.has_next():
print(iterator.next())
Client does not know:
- How numbers are stored
- Whether it’s a list or something else
- How traversal works
Only the iterator knows.
4. Multiple Traversal Strategies (Real Power)
Same collection, different iterators.
Reverse iterator
class ReverseNumberIterator(Iterator):
def __init__(self, numbers):
self._numbers = numbers
self._index = len(numbers) - 1
def has_next(self) -> bool:
return self._index >= 0
def next(self):
value = self._numbers[self._index]
self._index -= 1
return value
Collection can expose multiple iterators:
class NumberCollection(Aggregate):
def __init__(self, numbers):
self._numbers = numbers
def create_iterator(self):
return NumberIterator(self._numbers)
def create_reverse_iterator(self):
return ReverseNumberIterator(self._numbers)
No client code changes.
5. Python’s Built-in Iterator Pattern (Hidden but Everywhere)
Python already applies Iterator extensively.
Example:
for x in my_list:
print(x)
Under the hood:
my_list.__iter__()→ iteratoriterator.__next__()→ next elementStopIteration→ end
Custom iterator in Pythonic form:
class NumberIterator:
def __init__(self, numbers):
self.numbers = numbers
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.numbers):
raise StopIteration
value = self.numbers[self.index]
self.index += 1
return value
Client code stays identical:
for n in NumberIterator([1, 2, 3]):
print(n)
Iterator Pattern without ceremony.
6. When to Use Iterator (and When Not To)
Use Iterator when you see:
- “Client code shouldn’t know how this is stored.”
- “Traversal logic is duplicated everywhere.”
- “We need multiple traversal orders.”
- “Data structure is complex (tree, graph, composite).”
- “Encapsulation is being violated for iteration.”
Avoid Iterator when:
- Simple list/dict access is sufficient
- Traversal is trivial and unlikely to change
- Iterator adds more abstraction than value
- You only need one obvious traversal
Common pitfalls:
- Exposing internals through iterator
- Making iterator depend on mutable collection state
- Creating too many iterator types without need
- Reinventing iterators when language already provides them