Behavioral

Iterator

Provide a way to access elements of a collection sequentially without exposing its underlying representation.

Beginner Pattern #16 of 23

What is the Iterator Pattern?

Iterator extracts the traversal logic from a collection into a separate iterator object. Clients use a uniform interface (hasNext(), next()) to traverse any collection — whether it's a list, tree, graph, or database result set — without knowing the internal structure.

Multiple iterators can traverse the same collection independently and simultaneously, each maintaining their own position.

Real-world analogy: A TV remote's channel button. You press "next channel" without knowing how channels are stored internally (broadcast frequencies, cable mappings, satellite tables). The remote is your iterator.

The Problem It Solves

You have a tree data structure. Clients want to traverse it depth-first sometimes and breadth-first other times. Adding both traversal modes directly to the tree bloats the collection class and forces clients to know about traversal internals.

Iterator extracts each traversal strategy into its own iterator class. The tree just provides createDepthFirstIterator() and createBreadthFirstIterator() — the traversal logic lives outside.

Participants

Iterator (interface)
Declares the traversal interface: hasNext(), next(), optionally remove(). Clients only use this interface.
ConcreteIterator
Implements the traversal algorithm. Tracks the current position in the collection. Multiple instances can traverse the same collection independently.
IterableCollection (interface)
Declares the factory method createIterator() so clients can get an iterator without knowing the concrete collection type.
ConcreteCollection
Implements createIterator() to return a ConcreteIterator appropriate for its internal structure.

Visual Flow Diagram

«interface» Iterator hasNext(): boolean next(): T ConcreteIterator collection: ref position: int hasNext() / next() «interface» Collection createIterator(): Iterator add() / getItems() ConcreteCollection items: List<T> createIterator() → new ConcreteIterator(this) creates Client uses both interfaces

Java Code Example

Java — Custom Iterator + Java Iterable Integration
// Custom Iterator interface
public interface Iterator<T> {
    boolean hasNext();
    T       next();
}

// The collection
public class UserCollection implements Iterable<String> {
    private final List<String> users = new ArrayList<>();

    public void add(String user) { users.add(user); }

    // Forward iterator
    public java.util.Iterator<String> iterator() {
        return new ForwardIterator();
    }

    // Reverse iterator — extra traversal order
    public java.util.Iterator<String> reverseIterator() {
        return new ReverseIterator();
    }

    // Filter iterator — only users matching a predicate
    public java.util.Iterator<String> filterIterator(Predicate<String> pred) {
        return new FilterIterator(pred);
    }

    // ── ConcreteIterator: Forward ──────────────────────────
    private class ForwardIterator implements java.util.Iterator<String> {
        private int index = 0;
        public boolean hasNext() { return index < users.size(); }
        public String  next()    {
            if (!hasNext()) throw new NoSuchElementException();
            return users.get(index++);
        }
    }

    // ── ConcreteIterator: Reverse ──────────────────────────
    private class ReverseIterator implements java.util.Iterator<String> {
        private int index = users.size() - 1;
        public boolean hasNext() { return index >= 0; }
        public String  next()    {
            if (!hasNext()) throw new NoSuchElementException();
            return users.get(index--);
        }
    }

    // ── ConcreteIterator: Filter ───────────────────────────
    private class FilterIterator implements java.util.Iterator<String> {
        private final Predicate<String> pred;
        private int index = 0;
        private String next = null;

        FilterIterator(Predicate<String> pred) {
            this.pred = pred; advance();
        }

        private void advance() {
            next = null;
            while (index < users.size()) {
                String candidate = users.get(index++);
                if (pred.test(candidate)) { next = candidate; break; }
            }
        }
        public boolean hasNext() { return next != null; }
        public String  next()    {
            String r = next; advance(); return r;
        }
    }
}

// Client — uniform traversal regardless of iterator type
public class Main {
    public static void main(String[] args) {
        UserCollection users = new UserCollection();
        users.add("Alice"); users.add("Bob"); users.add("Charlie");

        // for-each uses iterator() automatically
        for (String u : users) System.out.print(u + " "); // Alice Bob Charlie

        // Reverse
        var rev = users.reverseIterator();
        while (rev.hasNext()) System.out.print(rev.next() + " "); // Charlie Bob Alice

        // Filtered — names starting with 'A'
        var filtered = users.filterIterator(u -> u.startsWith("A"));
        while (filtered.hasNext()) System.out.print(filtered.next()); // Alice
    }
}

When to Use / Avoid

✓ Use When

  • Collection has a complex internal structure you want to hide from clients
  • You need multiple traversal algorithms over the same collection
  • You want a uniform traversal interface across different collection types
  • Multiple clients need to traverse the same collection simultaneously

✕ Avoid When

  • Simple list traversal — Java's enhanced for-loop and Stream API are sufficient
  • The collection is tiny and traversal strategy will never change
  • You need random access by index — Iterator is sequential only

Real-World Examples

Pros & Cons

Pros

  • Single Responsibility — traversal logic separate from collection
  • Open/Closed — add new traversal types without changing collection
  • Multiple simultaneous iterations over same collection
  • Uniform client code regardless of collection type

Cons

  • Overkill for simple collections — Java for-each or Streams are cleaner
  • Concurrent modification during iteration throws ConcurrentModificationException
  • Stateful iterator objects can be misused if not consumed fully

How Iterator Can Be Broken

⚠ Attack Vectors

  • Concurrent modification: Collection is modified (add/remove) while an iterator is traversing it — Java's fail-fast iterators throw ConcurrentModificationException, but custom iterators may silently skip or duplicate elements
  • Calling next() without hasNext(): Iterator is advanced past the end — throws NoSuchElementException or returns null depending on implementation
  • Shared iterator across threads: Two threads call next() on the same iterator instance — both advance the position counter, causing elements to be skipped
  • remove() on unsupported iterator: Calling remove() on an iterator that doesn't support it throws UnsupportedOperationException at runtime with no compile-time warning
  • Infinite iterator with no termination: Iterator over a lazy/infinite sequence — client code that loops without a break condition runs forever

✓ Prevention

  • Use modCount guard: Store the collection's modification count at iterator creation — check it in next() and throw ConcurrentModificationException if changed (Java's approach)
  • Always check hasNext() before next(): Enforce this by contract documentation; or throw a descriptive IllegalStateException with a message rather than a bare NPE
  • Never share iterator across threads: Iterators are not thread-safe — document clearly; use CopyOnWriteArrayList or take a snapshot for concurrent scenarios
  • Defensive copy for concurrent iteration: Iterate over a copy (new ArrayList<>(original)) when modifications are expected during traversal
  • Bound infinite iterators: Always combine lazy/infinite iterators with a limit — use Stream.limit(n) or add a max-elements guard in hasNext()
Java — Break & Fix
// ❌ BREAKING — concurrent modification
for (String user : users) {
    if (user.startsWith("A")) users.remove(user); // ❌ ConcurrentModificationException
}

// ✅ FIX 1 — use iterator's own remove()
var it = users.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("A")) it.remove(); // ✅ safe
}

// ✅ FIX 2 — removeIf (Java 8+)
users.removeIf(u -> u.startsWith("A")); // ✅ cleanest approach

// ✅ modCount guard in custom iterator
private class SafeIterator implements java.util.Iterator<String> {
    private int index = 0;
    private final int expectedModCount = modCount; // snapshot at creation

    private void checkModification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

    public boolean hasNext() { checkModification(); return index < items.size(); }
    public String next()    { checkModification(); return items.get(index++); }
}

// ❌ BREAKING — next() without hasNext()
while (true) {
    System.out.println(it.next()); // ❌ NoSuchElementException when empty
}

// ✅ FIX — always guard with hasNext()
while (it.hasNext()) {
    System.out.println(it.next()); // ✅ safe
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Separates traversal from the collection. Iterator knows where it is; client just calls hasNext()/next(). Multiple iterators can traverse the same collection independently and simultaneously.
  2. Java built-in: java.util.Iterator<T> with hasNext(), next(), remove(). Implement Iterable<T> on your collection to enable for-each syntax. Java's iterators are fail-fast — they throw ConcurrentModificationException if collection changes during iteration.
  3. Critical rule: Never modify a collection while iterating with a for-each. Use iterator.remove() or Collection.removeIf() instead.