Skip to content
FollowtheDamnCode

Iterator

Walk the items of any collection one at a time, without knowing whether it's a list, a tree, or pages from an API.

Big idea

An Iterator gives one uniform way to visit a collection's elements in turn — "give me the next one" — while hiding how the collection is stored. The loop that consumes items doesn't care whether they come from an array, a tree walk, or a paginated web API fetched lazily.

The problem it solves

You have orders in three places: a Python list, a binary search tree keyed by date, and a REST API that returns 50 at a time. Code that totals them would otherwise need three loops: index arithmetic, a recursive walk, and a while next_page: loop with request logic. Each consumer re-implements the traversal, and changing the storage breaks every consumer.

Think of it like

A TV remote's "next channel" button. You don't need to know whether channels come over cable, satellite, or the internet, or how they're numbered internally — you press next, you get the next one. Different TVs (collections) can even offer different orders: by number, favourites only, recently watched.

Structure

Collections hand out iterators; clients only call next

Python builds this into the language: anything with __iter__ works in a for loop, and generator functions (yield) write the iterator for you.

In code

A tree and a paginated API behind one for-loop
class Node:
    def __init__(self, key, left=None, right=None):
        self.key, self.left, self.right = key, left, right


class OrderTree:
    def __init__(self, root):
        self.root = root
    def __iter__(self):                          # in-order traversal, lazily
        stack, node = [], self.root
        while stack or node:
            while node:
                stack.append(node); node = node.left
            node = stack.pop()
            yield node.key
            node = node.right


class OrderApi:                                  # pretend each page is a network call
    PAGES = {None: (["o-101", "o-102"], "p2"), "p2": (["o-103"], "p3"), "p3": (["o-104"], None)}
    def __init__(self):
        self.requests = 0
    def __iter__(self):
        token = None
        while True:
            self.requests += 1
            items, token = self.PAGES[token]
            yield from items                     # pages are fetched only as needed
            if token is None:
                return


tree = OrderTree(Node(20, Node(10, Node(5)), Node(30, None, Node(40))))
api = OrderApi()

def show(name, orders):                          # one consumer for every collection
    print(name, list(orders))

show("tree:", tree)
show("api: ", api)
print("api requests:", api.requests)

lazy = OrderApi()
first = next(iter(lazy))
print("first order:", first, "| requests so far:", lazy.requests)
output
tree: [5, 10, 20, 30, 40]
api:  ['o-101', 'o-102', 'o-103', 'o-104']
api requests: 3
first order: o-101 | requests so far: 1

Run and edit it in the app.

Asking for one order from the API made exactly one request — iterators are lazy, producing items only when asked.

Predict: two loops over one generator
def countdown(n):
    while n > 0:
        yield n
        n -= 1

numbers = countdown(3)
print(list(numbers))
print(list(numbers))
What does it print? Show the answer
[3, 2, 1]
[]

Run and edit it in the app.

A generator object is a single-use iterator: once it's exhausted, it stays exhausted. Collections like OrderTree hand out a fresh iterator on every for, which is why the iterable/iterator split matters.

What does the Iterator pattern hide from the code that consumes items?

Watch it

When to use it — and when not to

Use it whenever a collection's internal structure is complex or might change, when you need several traversal orders (in-order, breadth-first, filtered), or when items are expensive or infinite and should be produced lazily (pages, file lines, streams). For a plain list, the language's built-in iteration is already the pattern — don't write a class.

Pros and cons

ProsCons
One loop style for every collectionSingle-pass iterators surprise people when reused
Lazy: handles huge or infinite sequencesModifying a collection while iterating is error-prone
Several traversals of the same dataOverkill for simple arrays in languages with built-in loops

How it relates

  • Iterators walk Composite trees without exposing their structure.
  • Visitor performs operations on elements; Iterator just delivers them — they're often combined.
  • Factory Method is how a collection creates the right iterator (__iter__ is one).
  • Memento can capture an iterator's position to resume later.

Why is a generator a good iterator for a paginated API?

Key terms

  • Iterator — An object that returns a collection's elements one at a time.
  • Iterable — A collection that can produce a fresh iterator over itself.
  • Generator — A Python function using yield that produces an iterator automatically.
  • Lazy evaluation — Producing each item only when it's requested.