Behavioral Pattern Pattern Deep Dive

Iterator

Think of a TV remote's "channel up" button. You don't need to know whether channels are stored as a simple list, a satellite frequency table, or something more exotic underneath — you just press the button and get the next one. The Iterator pattern gives code the same ability over any collection: a consistent way to walk through elements one at a time — hasNext() / next(), or a language's built-in iteration protocol — without the calling code needing to know how the collection is actually stored.

⚠️ The Problem

Say you build a custom LinkedList class made of Node objects chained together with .next pointers. Anywhere that needs to loop over it has to know that internal detail: let node = list.head; while (node) { use(node.value); node = node.next; }. Every single place in your codebase that iterates the list is now coupled to "linked list made of nodes." If you later switch the internal storage to a plain array for performance, every one of those loops breaks and has to be rewritten.

✅ The Solution

Give the collection a method that returns an Iterator object — something with a consistent interface, like hasNext() and next(), or a language's built-in iteration protocol. The iterator, not the calling code, knows how to walk the collection's actual internal structure. Client code just calls the same two methods over and over (while (it.hasNext()) { it.next(); }) regardless of whether the thing underneath is a linked list, an array, or a tree — and if the internal structure changes later, only the iterator itself needs to be updated.

Example 1 — a custom linked list with a built-in iterator

Primary Example — JavaScript
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class LinkedList {
  #head = null;
  #tail = null;

  add(value) {
    const node = new Node(value);
    if (!this.#head) {
      this.#head = this.#tail = node;
    } else {
      this.#tail.next = node;
      this.#tail = node;
    }
    return this;
  }

  // Implementing Symbol.iterator makes this class work in for...of,
  // spread syntax, and anywhere else JavaScript expects an iterable.
  [Symbol.iterator]() {
    let current = this.#head;
    return {
      next() {
        if (!current) return { value: undefined, done: true };
        const value = current.value;
        current = current.next;
        return { value, done: false };
      },
    };
  }
}

const list = new LinkedList().add("a").add("b").add("c");

for (const item of list) {
  console.log(item); // "a", then "b", then "c"
}

console.log([...list]); // ["a", "b", "c"] — spread syntax works too

The for...of loop, and anything else expecting an iterable, has no idea LinkedList is chained Node objects internally — it just calls next() until done is true. If the internal storage changed to a plain array tomorrow, every for...of list loop anywhere in the app keeps working unchanged.

Example 2 — a secondary context: depth-first tree traversal

Secondary Example — Python
class TreeNode:
    def __init__(self, value, children=None):
        self.value = value
        self.children = children or []

class DepthFirstIterator:
    def __init__(self, root):
        self._stack = [root]

    def __iter__(self):
        return self

    def __next__(self):
        if not self._stack:
            raise StopIteration
        node = self._stack.pop()
        # Push children in reverse so the leftmost child is visited first.
        self._stack.extend(reversed(node.children))
        return node.value

class Tree:
    def __init__(self, root):
        self.root = root

    def __iter__(self):
        return DepthFirstIterator(self.root)

tree = Tree(TreeNode("root", [
    TreeNode("A", [TreeNode("A1"), TreeNode("A2")]),
    TreeNode("B"),
]))

for value in tree:
    print(value)  # root, A, A1, A2, B

Client code just writes for value in tree, exactly like it would for a list — the fact that a stack-based depth-first walk is happening underneath is entirely hidden inside DepthFirstIterator. Swapping to breadth-first traversal later means writing a different iterator class; the for value in tree call site never changes.

👍 When To Use It

  • You've built a custom collection type and want client code to loop over it without knowing its internal structure — arrays, linked lists, trees, graphs.
  • You want to support multiple ways of traversing the same collection (depth-first vs breadth-first over a tree, forward vs reverse over a list) without changing the collection's public API.
  • You want iteration to work uniformly with a language's existing loop syntax and library functions (for...of, spread, list(), list comprehensions) rather than a bespoke traversal method.

👎 When NOT To Use It

  • When the collection is already a built-in type (array, list, dictionary) — it already has a standard iterator; writing your own would just be reinventing what the language gives you for free.
  • When the whole collection is small and simple enough that exposing the raw internal structure directly causes no real coupling problem in practice.
  • When you need random access by index far more often than sequential traversal — a plain indexable structure is a better fit than forcing everything through a step-by-step iterator.

❓ FAQ

Isn't this just... how for-loops already work in most languages?

In modern languages, yes — many bake the Iterator pattern directly into the language itself: JavaScript's Symbol.iterator/for...of, Python's __iter__/__next__, C#'s IEnumerable/IEnumerator, and Java's Iterable/Iterator interfaces are all direct implementations of this pattern baked into the standard library. So you're very often using Iterator without ever writing one by hand — you only write a custom iterator when you build your own collection type that isn't already one of the built-in ones.

Can a collection support more than one kind of iterator?

Yes — nothing limits a collection to exactly one traversal order. A tree, for instance, can expose a depth-first iterator and a separate breadth-first iterator, or a method that takes a traversal strategy as a parameter. Client code picks whichever iterator fits the task without the collection's core data structure changing at all.

What happens if the collection changes while you're iterating over it?

This is a classic real-world gotcha, not something the pattern itself solves. Many languages either throw an error (Java, Python raise on concurrent modification) or produce undefined/surprising behavior if you mutate a collection mid-iteration. Iterators that need to tolerate concurrent changes typically snapshot the collection up front or use more careful locking — that's an implementation detail on top of the basic pattern, not part of its core definition.

🙋 There Are No Dumb Questions

Q: If my language already has for...of/foreach built in, do I ever need to write a custom Iterator myself?
A: Only when you build your own collection type that isn't already a built-in array, list, or dictionary — a custom linked list, tree, graph, or some domain-specific structure. For those, implementing your language's iteration protocol is exactly what lets your custom type plug into every existing loop, spread operator, and library function that already expects something iterable.

✅ Quick Check

What is the main benefit of the Iterator pattern for code that loops over a collection?


Next up: what happens when a group of UI components all need to react to each other, and giving each one a direct reference to every other one turns into a tangled web of dependencies? That's the Mediator pattern.

← Back to
Next →