Design Patterns, Explained Simply · Behavioral Patterns: How Objects Share Work

Iterator

Walk through the items of a collection one at a time without knowing how it's stored: an array, a tree, a database cursor, or pages from an API.

The problem

A reporting job needs every user from an API that returns 100 users per page. The first version loads all pages into one big list before processing. With 2 million users, it runs out of memory, and it can't start work until every page has downloaded.

Each caller also re-implements the paging logic ("is there a next page? what's the cursor?") in its own loop.

Real-world analogy

A TV remote's "next channel" button. You don't need to know how channels are stored or numbered; you press "next" until you've seen what you want. The remote (iterator) keeps track of where you are.

Without the pattern

users, page = [], 1
while True:
    batch = api.get_users(page)          # paging logic copied into every caller
    if not batch:
        break
    users.extend(batch)                  # everything held in memory at once
    page += 1
for user in users:
    process(user)

The pattern

  • Iterator: an object that knows the current position and gives the next item (next()), and says when there are no more.
  • Iterable collection: creates iterators (__iter__, [Symbol.iterator], iterator(), getIterator()).
  • Client: uses the language's loop (for ... in, for ... of, enhanced for, foreach) and never sees how items are stored or fetched.

Every modern language builds this pattern into its loops, and generators (yield) make writing iterators easy: the function pauses after each item and resumes when the next one is requested.

The code

Pages are fetched only when the loop needs them. The loop stops early after finding the user it wants, so page 3 is never fetched.

Lazily iterating over a paged APITry it yourself
PAGES = {1: ["ada", "bob"], 2: ["cy", "dee"], 3: ["eve", "fay"]}


def fetch_page(n):  # pretend this is an HTTP call
    print(f"  (fetching page {n})")
    return PAGES.get(n, [])


class PagedUsers:  # iterable: hides paging behind a normal loop
    def __iter__(self):
        page = 1
        while True:
            batch = fetch_page(page)
            if not batch:
                return
            yield from batch  # a generator: pauses after each user
            page += 1


for user in PagedUsers():
    print("processing", user)
    if user == "dee":
        print("found dee, stopping early")
        break

Output

  (fetching page 1)
processing ada
processing bob
  (fetching page 2)
processing cy
processing dee
found dee, stopping early

The caller wrote an ordinary loop. Paging, cursors and fetching are hidden in the iterator, and only two of the three pages were ever requested.

When to use it

  • A collection has a complex internal structure (tree, graph, paged API, database cursor, file stream) and callers just want "each item".
  • You want lazy processing of large or infinite sequences: items produced only as they're consumed.
  • You want several ways to traverse the same collection (depth-first vs breadth-first, filtered, reversed).

When not to use it

  • A plain list or array is enough. Every language already iterates those.
  • You need random access or the total count up front. Lazy iterators may not know the length until the end.
  • Be careful modifying a collection while iterating over it. Many iterators fail or skip items (Java throws ConcurrentModificationException).

You've already used it

  • Every for loop in Python, JavaScript, Java and PHP uses the iterator protocol: __iter__/__next__, Symbol.iterator, java.util.Iterator, PHP's Iterator/IteratorAggregate.
  • Generators: Python's yield, JavaScript's function*, PHP's yield.
  • Database cursors and Laravel's lazy() / cursor() stream rows instead of loading them all.
  • Node.js streams and async iterators (for await ... of) for files and HTTP responses.

Key points

  • Iterator gives sequential access to items without exposing how they're stored.
  • It enables lazy processing: fetch or compute items only when needed.
  • All four languages build it into their loops; generators make custom iterators short.
  • Further reading: Refactoring Guru: Iterator, PHP's Iterator interface and Java's Iterator.

Practice questions

What does the Iterator pattern give you over exposing a collection's internals?

Answer

Clients can loop over items with a standard interface without knowing whether the data is an array, a tree, a database cursor or a paged API, and the collection can change its storage without breaking callers.

How do generators relate to the Iterator pattern?

Answer

A generator function (yield in Python and PHP, function* in JavaScript) is a concise way to write an iterator: it pauses after producing each item and resumes when the next one is requested, keeping its position automatically.

Why is lazy iteration useful for large datasets?

Answer

Items are produced only when the consumer asks for them, so memory stays small (one page or row at a time), work can start immediately, and if the consumer stops early the rest is never fetched.

Open in the app to mark lessons complete, take notes and track your progress.