design·lab

Behavioral pattern

Iterator

One uniform way to walk through a collection's elements, one at a time, without the caller ever knowing whether it's stored as an array, a tree, or a linked list.

✗ The problem

Every collection is traversed its own way

An array walks by index. A tree walks by recursion. A linked list walks by chasing pointers. Client code has to know which — and repeat that knowledge everywhere it loops.

function printAll(coll) {
  if (Array.isArray(coll)) {
    for (let i = 0; i < coll.length; i++)
      show(coll[i]);          // index loop
  } else if (coll.root) {
    walkTree(coll.root);      // recursion
  } else if (coll.head) {
    let n = coll.head;
    while (n) { show(n.val); n = n.next; } // pointer chase
  }
}
The traversal logic leaks the collection's internals everywhere — swap the array for a tree and every caller like this one breaks.
✓ The pattern

Expose one uniform contract: next()

The collection hands out an iterator with a uniform contract — next() / hasNext(), or JS's built-in [Symbol.iterator]. The caller loops the same way no matter what's inside.

class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  [Symbol.iterator]() {
    let i = this.start, end = this.end;
    return {
      next: () => i <= end
        ? { value: i++, done: false }
        : { value: undefined, done: true }
    };
  }
}
Client
Iterator
next() / hasNext()
Collection
hidden internals
✓ See it live

Same loop, any collection

A Range(1, 6) hands out an iterator. Click Next to pull values one at a time — the client never touches start / end directly.

for (const n of range) {
  show(n);   // pulls one value at a time
}
1
2
3
4
5
6
cursor: not started
✓ Takeaway

Sequential access without exposing internals

  • Sequential access to elements without exposing how they're stored.
  • Multiple independent traversals — each iterator keeps its own cursor.
  • You use it constantly: JS for…of, generators, the spread operator, Java/Python iterators.
  • Careful: don't mutate a collection while an iterator over it is still in flight.
🎯 Principle applied: Iterator separates traversal from the collection (SRP) and hides internal structure (encapsulation), so storage can change without breaking callers.