Iterator Pattern

The Iterator is a behavioral design pattern that provides a way to access elements of a collection sequentially without exposing its underlying representation (e.g., array, list, tree). It decouples traversal logic from the collection, enabling multiple independent traversals and hiding internal structure.

Key Principles

  • Encapsulation: Collection manages iterator creation; client only uses iterator.
  • Single Responsibility: Collection focuses on storage; iterator on traversal.
  • Polymorphism: Different iterators for same collection (e.g., forward/reverse).

Structure

  • Iterator: Interface with methods like next(), has_next().
  • Concrete Iterator: Implements traversal for specific collection.
  • Aggregate: Interface for creating iterator.
  • Concrete Aggregate: Implements iterator factory method.

Python Example: Custom List Iterator

from typing import List, Any

# Iterator Interface
class Iterator:
    def has_next(self) -> bool:
        raise NotImplementedError

    def next(self) -> Any:
        raise NotImplementedError

# Concrete Iterator
class ListIterator(Iterator):
    def __init__(self, collection: List[Any]):
        self._collection = collection
        self._index = 0

    def has_next(self) -> bool:
        return self._index < len(self._collection)

    def next(self) -> Any:
        if not self.has_next():
            raise StopIteration
        item = self._collection[self._index]
        self._index += 1
        return item

# Aggregate Interface
class IterableCollection:
    def create_iterator(self) -> Iterator:
        raise NotImplementedError

# Concrete Aggregate
class CustomList(IterableCollection):
    def __init__(self):
        self._items: List[Any] = []

    def add(self, item: Any):
        self._items.append(item)

    def create_iterator(self) -> Iterator:
        return ListIterator(self._items)

# Client Code
if __name__ == "__main__":
    my_list = CustomList()
    my_list.add("Apple")
    my_list.add("Banana")
    my_list.add("Cherry")

    iterator = my_list.create_iterator()
    while iterator.has_next():
        print(f"Item: {iterator.next()}")

    # Multiple independent iterators
    iter1 = my_list.create_iterator()
    iter2 = my_list.create_iterator()
    print(f"First from iter1: {next(iter1)}")   # Apple
    print(f"First from iter2: {next(iter2)}")   # Apple

Output:

Item: Apple
Item: Banana
Item: Cherry
First from iter1: Apple
First from iter2: Apple

When to Use Iterator

  • Hide Internal Structure: Traverse trees, graphs without exposing nodes.
  • Multiple Traversals: Independent iterators (e.g., two loops over same data).
  • Uniform Access: Same interface for different collections.
  • Avoid: Simple lists where built-in iteration suffices.

Summary: Iterator separates traversal from collection, providing a standard way to iterate over elements while keeping implementation details hidden. In Python, it's built-in via __iter__() and __next__(), but the pattern is explicit in custom structures.