StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsIterator

Iterator

A behavioral pattern that hands traversal to a bookmark object, so collections keep their internals private and every for-each loop works the same way.

Definition

Of the 23 patterns in the catalog, this is the one you've already used today, because every for-each loop in every modern language runs on it. Iterator is the Gang of Four behavioral pattern whose official intent reads: provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. The collection keeps its insides private, and traversal becomes somebody else's job.


An iterator is really just a bookmark object: it remembers where you are in a collection and hands you the next element when asked. The collection vends fresh bookmarks on demand, each loop holds its own, and the structure being walked, array, linked list, tree, or something lazier, stays a private implementation detail behind two small questions.

Clients Chasing Pointers

Here's the pattern's absence. A playlist stores song durations in a linked list whose head is public, and every function that needs the songs walks the nodes itself, pointer by pointer:

Both functions contain the same traversal loop, and so will the next ten functions anyone writes. The deeper cost is the coupling: every caller now knows the playlist is a linked list with fields named head and next, so the day profiling says an array would be faster, the change breaks every caller simultaneously. The collection's most private decision, how it stores things, has become everyone's business.

The Pattern Your Language Absorbed

The iterator version makes the node structure private and exposes one thing: a way to get a bookmark. What's striking across the tabs is that every language has absorbed the pattern into syntax, each with its own spelling. Python's iterator protocol drives for/in, JavaScript's Symbol.iterator drives for/of, and Java's Iterator interface drives for-each. The example adds three durations, 210, 185, and 240. Before reading past the code, predict the total and count how many times the client touches a node:

The total is 635 and the client touches zero nodes, because the for-loop machinery pulls values through the bookmark while the linked list stays invisible. Swap the list for an array now and only the aggregate's own methods change. Note what exhaustion looks like per tab: Java throws NoSuchElementException past the end, Python generators raise StopIteration automatically, JavaScript yields done: true, and Rust's next() returns None, four costumes on the same contract.

Who Does What

Four roles. The Iterator interface declares the traversal contract. ConcreteIterators implement it while holding the position, like the Java tab's anonymous class with its current field or Rust's PlaylistIter. The Aggregate declares the iterator-vending method, and ConcreteAggregates (Playlist) return fresh iterators over their private structure. One aggregate can vend many iterator flavors, forward, reversed, filtered, each a different walk over the same data.


The pattern works like a museum audio guide: it leads you exhibit to exhibit, you never consult the floor plan, and two visitors with two guides can stand in different rooms of the same museum at once, which is exactly two iterators mid-walk over one collection. The analogy's limit is the museum's stillness. Collections get mutated mid-walk, and the Java documentation answers that case with the word unspecified: modify a collection during iteration by any route other than the iterator's own remove() and the behavior is formally anyone's guess.

Iterator: Step-by-Step

Installing the pattern is four moves:

1. Move the Bookmark Out of the Collection

The iterator object owns the position, not the playlist. That separation is what lets two loops walk the same collection simultaneously without trampling each other's place.

2. Answer Two Questions: More? Next?

Every iterator interface reduces to the same pair, whether spelled hasNext()/next(), __next__() with StopIteration, or next() returning an Option. The vocabulary varies, the contract doesn't.

3. Signal Exhaustion the Language's Way

Java throws NoSuchElementException, Python raises StopIteration, JavaScript returns done: true, Rust returns None. Pick your language's convention, because the for-loop machinery is listening for exactly that signal.

4. Vend Fresh Iterators on Demand

The aggregate's job is a factory method like __iter__() or iterator() that hands out a new bookmark each time. Every for-loop gets its own, which is why nested loops over one collection just work.
Pull, Push, and the Generator Trick

Iteration comes in two directions of control. External iteration pulls: the client holds the bookmark and calls next() when it wants more, which is what every for-each desugars to. Internal iteration pushes: the collection drives, calling your function once per element, the way forEach and map do. Pull composes better, since the client can stop, interleave two iterators, or hand the bookmark to someone else, while push is simpler to provide.


Generators are the trick that collapses the difference: you write push-style code, a plain loop with yield in it, and the language packages it as a pull iterator. Python's docs are explicit that a generator-based __iter__() supplies __next__() and StopIteration automatically, which is why the Python, C#, PHP, and TypeScript tabs contain no bookkeeping at all. The bookmark still exists, but the compiler is the one writing it.

The Go and Rust Versions

Go held out against this pattern for over a decade, then absorbed it like everyone else. Go 1.23's range-over-func makes an iterator a function of type iter.Seq that pushes values to a yield callback until told to stop, and the range keyword now drives it. The Go blog names the push-pull split directly and ships iter.Pull for converting one to the other, which makes Go the language where the two iteration directions are most explicitly first-class.


Rust made the iterator its crown jewel. The Iterator trait demands one method, next() returning Option, and pays out roughly seventy adapter methods for it: map, filter, zip, chain, and the sum() the code tab uses instead of a loop. Every Rust for-loop desugars through IntoIterator into repeated next() calls, which means the GoF pattern isn't just available in Rust, it is the language's entire collection-processing model.

Best Practices

The protocol is two questions, and these five keep the answers trustworthy:


A hand-written iterator class is bookkeeping a yield does for free: Python, C#, PHP, and JavaScript generators all implement the protocol from a plain loop body. Write the class only when the language offers nothing better, the way Java's anonymous iterator does.

Check Yourself

Two questions before you go. First: the client summed 635 without touching a node, so where does the traversal position live, and why does that location matter the moment two loops walk the playlist at once? Second: your for-each loop is external iteration, but a generator is written like internal iteration. What does the language do to bridge the two? Both answers are on this page.


Then go spot it in the wild, which takes about four seconds: every for/in, for/of, for-each, and range loop you can see in your editor right now is driving an iterator the language conjured. The more interesting hunt is the before-block's shape: find a class in your codebase whose callers reach into .items or .children and loop over the raw structure directly. Each of those call sites is a traversal that breaks the day the structure changes, and one __iter__() would orphan none of them.

the_exposed_internals.py

class Node:
    def __init__(self, value: int):
        self.value = value
        self.next: "Node | None" = None

class Playlist:
    def __init__(self):
        self.head: Node | None = None  # public, and that's the problem

def total_duration(playlist: Playlist) -> int:
    # The caller chases the playlist's pointers itself
    total = 0
    node = playlist.head
    while node is not None:
        total += node.value
        node = node.next
    return total

def print_titlesque(playlist: Playlist) -> None:
    # And so does every other caller, identically
    node = playlist.head
    while node is not None:
        print(node.value)
        node = node.next

# Swap the linked list for an array tomorrow and every
# caller that ever touched .head and .next breaks at once

iterator_(language-native).py

class Node:
    def __init__(self, value: int):
        self.value = value
        self.next: "Node | None" = None

class Playlist:  # Aggregate: the node structure is private now
    def __init__(self):
        self._head: Node | None = None
        self._tail: Node | None = None

    def add(self, duration: int) -> None:
        node = Node(duration)
        if self._tail is None:
            self._head = node
        else:
            self._tail.next = node
        self._tail = node

    def __iter__(self):
        # A generator implements the iterator protocol for free:
        # __next__ and StopIteration come built in
        node = self._head
        while node is not None:
            yield node.value
            node = node.next

# Example usage: the client never sees a node
playlist = Playlist()
playlist.add(210)
playlist.add(185)
playlist.add(240)

total = 0
for duration in playlist:
    total += duration
print(total)  # 635