skip to content

Iterator Pattern - Decouple Traversal from Structure

Updated: at 12:00 AM

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:

We want to iterate without knowing:

2. The Iterator Pattern: Decouple Traversal from Structure

The Iterator Pattern separates how data is stored from how it is traversed.

Core idea:

A system using Iterator has two parts:

The client only talks to the iterator, not the collection internals.

You get:

Use Iterator when:

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:

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:

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:

Avoid Iterator when:

Common pitfalls: