Iterator
Provides a way to access elements of a collection sequentially.
Iterator Pattern: A Complete Guide with Examples in 11 Programming Languages
The Iterator Pattern is a behavioral design pattern that provides a standardized way to sequentially access elements of a collection without exposing the collection's underlying representation. Whether the data lives in an array, a linked list, a tree, a database cursor, a file on disk, or a lazily generated infinite sequence, the consumer always interacts with the same simple interface: ask for the next element, check if there are more. This uniform traversal contract is so fundamental that virtually every modern language has it baked directly into its core syntax — Python's for loop, JavaScript's for...of, Java's enhanced for, Rust's for loop, and Swift's for-in are all iterator protocol implementations under the hood.
In this comprehensive guide, we'll explore the Iterator Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and when to use this powerful pattern.
Table of Contents
- What is the Iterator Pattern?
- Why Use the Iterator Pattern?
- Iterator Pattern Comparison
- Iterator Pattern Explained
- Class Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is the Iterator Pattern?
The Iterator Pattern separates the concern of how to traverse a collection from what to do with each element. A collection (the Aggregate) creates an Iterator object that remembers the current position within the traversal. The client repeatedly calls next() on the iterator to advance through elements, and has_next() to check if the traversal is complete.
Think of it like a reading bookmark. The book (the collection) doesn't know how fast you read or where you are. The bookmark (the iterator) tracks your exact position. You can have multiple bookmarks in the same book — each remembers an independent position. You can even have bookmarks that traverse the book in different orders: front to back, back to front, or jumping chapter by chapter.
Why Use the Iterator Pattern?
The Iterator Pattern offers several compelling benefits:
- Single Responsibility: Traversal logic lives in the iterator, not the collection — collections focus on storage, iterators focus on traversal
- Multiple Simultaneous Traversals: Multiple independent iterators can traverse the same collection at different positions concurrently without interfering with each other
- Uniform Interface: Clients use the same interface regardless of whether the backing collection is an array, tree, graph, file, network stream, or generated sequence
- Lazy Evaluation: Iterators can generate or fetch elements on demand — processing infinite sequences or large datasets without loading everything into memory
- Alternative Traversal Strategies: The same collection can have multiple iterator types (forward, reverse, filtered, depth-first, breadth-first) without changing the collection's interface
- Algorithm Decoupling: Sorting, searching, transforming, and aggregating algorithms written against the iterator interface work with any collection type
- Language Integration: Implementing the iterator protocol makes your custom collections work seamlessly with built-in
forloops, spread operators, and stream APIs
Iterator Pattern Comparison
Let's compare the Iterator Pattern with related behavioral patterns:
| Pattern | Purpose | Who Controls Traversal | State Location | Use Case |
|---|---|---|---|---|
| Iterator | Sequential access to elements | Client (pulls) | Iterator object | Traversing collections, lazy sequences |
| Composite | Tree structure with uniform interface | Client walks tree | Tree structure | File systems, UI hierarchies |
| Visitor | Operations on object structure | Visitor (pushes) | Visitor object | AST transforms, reporting |
| Generator | Lazily produce a sequence | Runtime (coroutine) | Coroutine frame | Infinite sequences, pipelines |
| Observer | Notify subscribers of events | Publisher (pushes) | Subscriber list | Event systems, reactive UI |
Key Distinctions:
- Iterator vs. for-loop over array: Both traverse linearly. Iterator abstracts what is being traversed — the same loop body works on arrays, trees, database cursors, and HTTP paginated APIs. A for-loop over an array is hardcoded to that structure.
- Iterator vs. Generator/Coroutine: Generators ARE iterators produced by a special coroutine mechanism. An iterator is the interface; a generator is one way to implement it. All generators are iterators; not all iterators are generators.
- Pull (Iterator) vs. Push (Observer): Iterator is pull-based — the client requests the next item. Observer is push-based — the publisher delivers items to subscribers. Reactive streams (RxJava, RxJS) bridge both worlds.
Iterator Pattern Explained
The Iterator Pattern typically involves:
Core Components:
- Iterator Interface: Declares the traversal operations — minimally
next()(return next element and advance) andhas_next()/done(check if more elements exist). Often also includescurrent()(peek at current without advancing) andreset(). - Concrete Iterator: Implements the Iterator interface for a specific collection. Maintains a reference to the collection and the current traversal position (index, pointer, cursor). Each instance is an independent traversal.
- Aggregate Interface (Iterable/Collection): Declares the factory method that creates an iterator — typically
__iter__(),iterator(),[Symbol.iterator](), ormakeIterator(). The collection doesn't do the traversal; it just manufactures the iterator. - Concrete Aggregate: Implements the Iterable interface. Stores the elements. Returns a new iterator instance on each call to the factory method.
- Client: Uses only the Iterator interface to traverse. Never interacts with the concrete collection internals.
Iterator Variants:
- External Iterator: Client controls traversal by calling
next()explicitly — maximum control, more verbose code - Internal Iterator: Iterator controls traversal and calls a callback for each element (e.g.,
.forEach()) — simpler client code, less control - Forward Iterator: Traverses front to back (most common)
- Reverse Iterator: Traverses back to front
- Filtering Iterator: Skips elements that don't match a predicate without materializing a filtered copy
- Transforming Iterator: Maps each element through a function lazily
- Composite Iterator: Traverses multiple collections as if they were one
- Lazy/Infinite Iterator: Generates elements on demand — may never terminate
Class Diagram
Here's the UML class diagram showing the Iterator structure:
Beginner-Friendly Example
Let's start with a classic example: a custom book collection with multiple iterator types — a standard forward iterator, a reverse iterator, and a filtered iterator that only yields books in a given genre. This demonstrates the core value of the pattern: the same for loop body works with all three traversal strategies.
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Optional, Iterator as TypingIterator, List
from dataclasses import dataclass
T = TypeVar("T")
@dataclass
class Book:
title: str
author: str
genre: str
pages: int
def __repr__(self) -> str:
return f'"{self.title}" by {self.author} [{self.genre}, {self.pages}pp]'
# ── Iterator Interface ────────────────────────────────────────────
class BookIterator(ABC):
@abstractmethod
def __iter__(self) -> "BookIterator":
pass
@abstractmethod
def __next__(self) -> Book:
pass
def has_next(self) -> bool:
"""Convenience method — not required by Python's protocol."""
try:
# Peek ahead without advancing (peek by trying and caching)
return True
except StopIteration:
return False
# ── Concrete Iterators ────────────────────────────────────────────
class ForwardBookIterator(BookIterator):
def __init__(self, books: List[Book]) -> None:
self._books = books
self._position = 0
def __iter__(self) -> "ForwardBookIterator":
return self
def __next__(self) -> Book:
if self._position >= len(self._books):
raise StopIteration
book = self._books[self._position]
self._position += 1
return book
class ReverseBookIterator(BookIterator):
def __init__(self, books: List[Book]) -> None:
self._books = books
self._position = len(books) - 1
def __iter__(self) -> "ReverseBookIterator":
return self
def __next__(self) -> Book:
if self._position < 0:
raise StopIteration
book = self._books[self._position]
self._position -= 1
return book
class GenreFilterIterator(BookIterator):
"""Lazy filtering iterator — skips non-matching books without a copy."""
def __init__(self, books: List[Book], genre: str) -> None:
self._books = books
self._genre = genre.lower()
self._position = 0
def __iter__(self) -> "GenreFilterIterator":
return self
def __next__(self) -> Book:
while self._position < len(self._books):
book = self._books[self._position]
self._position += 1
if book.genre.lower() == self._genre:
return book
raise StopIteration
class LongBookIterator(BookIterator):
"""Yields only books above a minimum page count."""
def __init__(self, books: List[Book], min_pages: int) -> None:
self._books = books
self._min_pages = min_pages
self._position = 0
def __iter__(self) -> "LongBookIterator":
return self
def __next__(self) -> Book:
while self._position < len(self._books):
book = self._books[self._position]
self._position += 1
if book.pages >= self._min_pages:
return book
raise StopIteration
# ── Concrete Collection ───────────────────────────────────────────
class BookShelf:
"""The Aggregate — stores books and creates iterators."""
def __init__(self) -> None:
self._books: List[Book] = []
def add(self, book: Book) -> "BookShelf":
self._books.append(book)
return self # Fluent API
def __len__(self) -> int:
return len(self._books)
# Factory methods — each returns a different iterator type
def __iter__(self) -> ForwardBookIterator:
"""Default iteration: forward."""
return ForwardBookIterator(self._books)
def reversed_iter(self) -> ReverseBookIterator:
return ReverseBookIterator(self._books)
def genre_iter(self, genre: str) -> GenreFilterIterator:
return GenreFilterIterator(self._books, genre)
def long_books_iter(self, min_pages: int = 400) -> LongBookIterator:
return LongBookIterator(self._books, min_pages)
# ── Client ────────────────────────────────────────────────────────
def print_books(label: str, iterator: BookIterator) -> None:
print(f"\n{label}:")
for book in iterator:
print(f" {book}")
if __name__ == "__main__":
shelf = BookShelf()
shelf.add(Book("The Hobbit", "Tolkien", "Fantasy", 310))
shelf.add(Book("Dune", "Herbert", "Sci-Fi", 688))
shelf.add(Book("1984", "Orwell", "Dystopia", 328))
shelf.add(Book("Foundation", "Asimov", "Sci-Fi", 244))
shelf.add(Book("The Name of the Wind", "Rothfuss", "Fantasy", 662))
shelf.add(Book("Brave New World", "Huxley", "Dystopia", 311))
shelf.add(Book("Hyperion", "Simmons", "Sci-Fi", 482))
print(f"Shelf has {len(shelf)} books.")
print_books("All books (forward)", iter(shelf))
print_books("All books (reverse)", shelf.reversed_iter())
print_books("Sci-Fi only", shelf.genre_iter("sci-fi"))
print_books("Dystopia only", shelf.genre_iter("dystopia"))
print_books("Long books (400+ pp)", shelf.long_books_iter(400))
# Multiple independent iterators on the same shelf
print("\n--- Two independent iterators running simultaneously ---")
it1 = iter(shelf)
it2 = shelf.genre_iter("sci-fi")
print(f"it1 next: {next(it1)}")
print(f"it2 next: {next(it2)}")
print(f"it1 next: {next(it1)}")
print(f"it2 next: {next(it2)}")
Production-Ready Example
Now let's tackle a realistic scenario: a lazy paginated API iterator. When fetching large result sets from a REST API (users, orders, log entries), you don't want to load all pages into memory upfront. A paginated iterator fetches one page at a time, transparently to the consumer — the for loop just keeps asking for the next item and the iterator handles page boundaries, fetch-on-demand, and end-of-results detection.
🐍 Python (Production Example)
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional, List, Iterator, TypeVar, Generic, Callable, Dict, Any
import math
import time
T = TypeVar("T")
# ─── Simulated API ────────────────────────────────────────────────
@dataclass
class User:
id: int
name: str
email: str
active: bool
plan: str
def __repr__(self) -> str:
status = "✓" if self.active else "✗"
return f"User(id={self.id:3}, {status} {self.plan:8} | {self.name} <{self.email}>)"
@dataclass
class PageResult(Generic[T]):
items: List[T]
page: int
page_size: int
total_items: int
@property
def total_pages(self) -> int:
return math.ceil(self.total_items / self.page_size)
@property
def has_next(self) -> bool:
return self.page < self.total_pages
class FakeUserAPI:
"""Simulates a paginated REST API backed by an in-memory dataset."""
def __init__(self) -> None:
self._users = [
User(i, f"User {i:03}", f"user{i:03}@example.com",
active=(i % 3 != 0),
plan=["free", "basic", "premium"][i % 3])
for i in range(1, 51) # 50 users total
]
self._fetch_count = 0
def get_page(self, page: int, page_size: int = 10,
filters: Optional[Dict[str, Any]] = None) -> PageResult[User]:
"""Simulates a network call with latency."""
self._fetch_count += 1
time.sleep(0.01) # Simulated network latency
data = self._users
if filters:
if "plan" in filters:
data = [u for u in data if u.plan == filters["plan"]]
if "active" in filters:
data = [u for u in data if u.active == filters["active"]]
start = (page - 1) * page_size
end = start + page_size
page_data = data[start:end]
return PageResult(
items=page_data,
page=page,
page_size=page_size,
total_items=len(data)
)
@property
def total_fetches(self) -> int:
return self._fetch_count
# ─── Paginated Iterator ────────────────────────────────────────────
class PaginatedIterator(Generic[T]):
"""
Lazily iterates over all pages of a paginated API.
Fetches the next page only when the current page is exhausted.
Fully implements Python's iterator protocol.
"""
def __init__(
self,
fetch_page: Callable[[int], PageResult[T]],
start_page: int = 1,
) -> None:
self._fetch_page = fetch_page
self._current_page = start_page
self._page_items: List[T] = []
self._item_pos = 0
self._exhausted = False
self._pages_fetched = 0
self._items_yielded = 0
# Pre-fetch the first page
self._load_next_page()
def _load_next_page(self) -> None:
result = self._fetch_page(self._current_page)
self._pages_fetched += 1
self._page_items = result.items
self._item_pos = 0
if not result.items or not result.has_next:
# This is the last page — mark exhausted after current items are consumed
self._last_page = True
else:
self._last_page = False
self._current_page += 1
def __iter__(self) -> "PaginatedIterator[T]":
return self
def __next__(self) -> T:
# If we've consumed all items on the current page, try to load the next
if self._item_pos >= len(self._page_items):
if self._last_page or self._exhausted:
self._exhausted = True
raise StopIteration
self._load_next_page()
# If page loaded empty (shouldn't happen with a well-behaved API)
if not self._page_items:
self._exhausted = True
raise StopIteration
item = self._page_items[self._item_pos]
self._item_pos += 1
self._items_yielded += 1
return item
@property
def stats(self) -> Dict[str, int]:
return {
"pages_fetched": self._pages_fetched,
"items_yielded": self._items_yielded,
}
# ─── Iterable Wrapper ─────────────────────────────────────────────
class PaginatedCollection(Generic[T]):
"""
Iterable wrapper so PaginatedIterator can be used in for-loops.
Each iteration creates a fresh iterator starting from page 1.
"""
def __init__(
self,
fetch_fn: Callable[[int], PageResult[T]],
) -> None:
self._fetch_fn = fetch_fn
self._last_iter: Optional[PaginatedIterator[T]] = None
def __iter__(self) -> PaginatedIterator[T]:
self._last_iter = PaginatedIterator(self._fetch_fn)
return self._last_iter
def take(self, n: int) -> List[T]:
"""Convenience method: fetch only the first N items."""
result = []
for item in self:
result.append(item)
if len(result) >= n:
break
return result
@property
def last_iterator_stats(self) -> Optional[Dict[str, int]]:
return self._last_iter.stats if self._last_iter else None
# ─── Composite Iterator: Merge multiple paginated sources ─────────
class MergedIterator(Generic[T]):
"""
Merges multiple iterators into a single sequential stream.
Useful for combining results from multiple API endpoints.
"""
def __init__(self, *iterables: "PaginatedCollection[T]") -> None:
self._iterables = iterables
self._current_iter: Optional[Iterator[T]] = None
self._source_idx = 0
self._advance_source()
def _advance_source(self) -> None:
if self._source_idx < len(self._iterables):
self._current_iter = iter(self._iterables[self._source_idx])
self._source_idx += 1
else:
self._current_iter = None
def __iter__(self) -> "MergedIterator[T]":
return self
def __next__(self) -> T:
while self._current_iter is not None:
try:
return next(self._current_iter)
except StopIteration:
self._advance_source()
raise StopIteration
# ─── Client ────────────────────────────────────────────────────────
if __name__ == "__main__":
api = FakeUserAPI()
# ── Scenario 1: Iterate ALL users lazily (5 pages × 10 items) ──
print("=" * 60)
print("SCENARIO 1: All users — lazy pagination")
print("=" * 60)
all_users = PaginatedCollection(
lambda page: api.get_page(page, page_size=10)
)
count = 0
for user in all_users:
count += 1
if count <= 3 or count >= 49:
print(f" {user}")
elif count == 4:
print(" ...")
stats = all_users.last_iterator_stats
print(f"\n Total users: {count}")
print(f" Pages fetched: {stats['pages_fetched']}")
print(f" API calls so far: {api.total_fetches}")
# ── Scenario 2: Take only first 15 items (only 2 pages fetched) ──
print("\n" + "=" * 60)
print("SCENARIO 2: Take only first 15 — stops after 2 pages")
print("=" * 60)
api2 = FakeUserAPI()
premium_users = PaginatedCollection(
lambda page: api2.get_page(page, page_size=10, filters={"plan": "premium"})
)
first_15 = premium_users.take(15)
print(f" Fetched {len(first_15)} premium users:")
for user in first_15[:3]:
print(f" {user}")
print(f" ...")
stats2 = premium_users.last_iterator_stats
print(f" Pages fetched to get 15 items: {stats2['pages_fetched']}")
print(f" API calls: {api2.total_fetches} (vs {math.ceil(50/10)} if fully loaded)")
# ── Scenario 3: Active users only, small page size ──
print("\n" + "=" * 60)
print("SCENARIO 3: Active users only, page_size=5")
print("=" * 60)
api3 = FakeUserAPI()
active_users = PaginatedCollection(
lambda page: api3.get_page(page, page_size=5, filters={"active": True})
)
active_count = sum(1 for _ in active_users)
print(f" Active users: {active_count}")
print(f" Pages fetched: {active_users.last_iterator_stats['pages_fetched']}")
# ── Scenario 4: Lazy chaining with standard Python itertools ──
print("\n" + "=" * 60)
print("SCENARIO 4: Standard library interop (itertools)")
print("=" * 60)
import itertools
api4 = FakeUserAPI()
all_users4 = PaginatedCollection(lambda page: api4.get_page(page, page_size=10))
# islice lazily takes the first N — only fetches what's needed
first_5 = list(itertools.islice(all_users4, 5))
print(f" First 5 (via islice): {[u.name for u in first_5]}")
print(f" Pages fetched: {all_users4.last_iterator_stats['pages_fetched']} (just 1!)")
# Chain two filtered collections as if they were one
api5 = FakeUserAPI()
free_users = PaginatedCollection(lambda p: api5.get_page(p, page_size=5, filters={"plan": "free"}))
premium_users2= PaginatedCollection(lambda p: api5.get_page(p, page_size=5, filters={"plan": "premium"}))
combined = list(itertools.chain(
itertools.islice(free_users, 3),
itertools.islice(premium_users2, 3)
))
print(f"\n Combined (3 free + 3 premium): {[u.name for u in combined]}")
Real-World Use Cases
1. Database Cursors
Every database driver's query result is an iterator. Python's sqlite3 cursor, Java's ResultSet, and Go's sql.Rows all implement the iterator protocol. Data is fetched in chunks from the database server rather than loaded all at once.
import sqlite3
conn = sqlite3.connect(":memory:")
cursor = conn.execute("SELECT * FROM users WHERE active = 1")
for row in cursor: # cursor IS an iterator
process(row)
2. File System Traversal
os.walk() in Python, Files.walk() in Java, and filepath.Walk() in Go are tree iterators over file system hierarchies. They yield directories and files lazily without loading the entire directory tree into memory.
import os
for dirpath, dirs, files in os.walk("/var/log"): # Lazy tree iterator
for filename in files:
process(os.path.join(dirpath, filename))
3. HTTP Pagination / API Clients
SDKs for AWS, GitHub, Stripe, and Salesforce wrap paginated API endpoints in iterators. The caller writes a simple for loop; the SDK handles cursor tracking, rate limiting, and page fetching.
# GitHub's PyGithub SDK
for commit in repo.get_commits(): # PaginatedList IS an iterator
print(commit.sha) # Fetches pages on demand
4. Stream Processing Pipelines
Kafka consumers, log processors, and ETL pipelines treat data as iterators. Each stage in the pipeline is a transforming iterator: parse → filter → enrich → validate → sink.
5. Lazy Data Generation
Generators in Python, Sequence in Kotlin, and lazy {} in Swift produce values on demand. Infinite mathematical sequences (fibonacci(), primes()) are only feasible as iterators — there's no "all Fibonacci numbers" to materialize.
def fibonacci() -> Iterator[int]:
a, b = 0, 1
while True: # Infinite iterator
yield a
a, b = b, a + b
import itertools
first_10_fibs = list(itertools.islice(fibonacci(), 10))
6. UI Component Trees
React's virtual DOM reconciler, Android's RecyclerView.Adapter, and iOS's UITableViewDataSource are all iterator-like abstractions: give me item at index N, tell me the total count. The UI framework controls traversal; the data source provides items on demand.
7. Parser Token Streams
Lexers (tokenizers) return iterators of tokens. Parsers call next_token() to advance through the input stream without loading the entire file. This is how every compiler and interpreter processes source code.
Language-Specific Mistakes and Anti-Patterns
❌ Mistake #1: Returning self from __iter__ on the Collection
# BAD: Collection IS its own iterator — only one traversal at a time!
class BookShelf:
def __init__(self):
self._books = []
self._pos = 0 # Traversal state on the collection!
def __iter__(self):
self._pos = 0
return self # Returning self — the shelf IS the iterator
def __next__(self):
if self._pos >= len(self._books):
raise StopIteration
book = self._books[self._pos]
self._pos += 1
return book
# Now you can't nest loops or have two independent traversals:
for book1 in shelf:
for book2 in shelf: # Resets shelf._pos — outer loop gets confused!
...
# GOOD: Separate the collection from the iterator
class BookShelf:
def __iter__(self):
return ForwardBookIterator(self._books) # New iterator each time!
❌ Mistake #2: Not Implementing __iter__ on the Iterator Itself
# BAD: Iterator doesn't implement __iter__ — can't be used in for-loops directly
class MyIterator:
def __next__(self) -> Book:
... # Only __next__, no __iter__
for book in MyIterator(books): # TypeError: 'MyIterator' object is not iterable!
# GOOD: Iterator must implement both __iter__ (returning self) and __next__
class MyIterator:
def __iter__(self):
return self # Iterators return themselves from __iter__
def __next__(self) -> Book:
...
❌ Mistake #3: Consuming a Generator Twice
# BAD: Generators are exhausted after first iteration — second loop gets nothing
def sci_fi_books(shelf):
for book in shelf:
if book.genre == "Sci-Fi":
yield book
sci_fi = sci_fi_books(shelf)
print(list(sci_fi)) # [Book1, Book2, Book3]
print(list(sci_fi)) # [] ← Empty! Generator was exhausted!
# GOOD: If you need to iterate multiple times, either:
# 1. Recreate the generator each time
sci_fi_list = list(sci_fi_books(shelf)) # Materialize into a list once
print(sci_fi_list)
print(sci_fi_list) # Both work
# 2. Or wrap in a factory function / class with __iter__
class SciFiBooks:
def __init__(self, shelf): self._shelf = shelf
def __iter__(self):
return (book for book in self._shelf if book.genre == "Sci-Fi")
Frequently Asked Questions
Q1: What is the difference between an Iterator and an Iterable?
They are complementary but distinct:
An Iterable is a collection that knows how to create an iterator. It has a factory method (__iter__, iterator(), [Symbol.iterator](), makeIterator()). You can iterate it multiple times — each call to the factory gives a fresh, independent iterator.
An Iterator is an object that does the traversal. It has state (current position) and advance methods (__next__, next(), hasNext()). Once exhausted, it's done — you typically don't reset it; you create a new one from the Iterable.
Rule of thumb: for (item in X) needs an Iterable. iter.next() gives you the next item from an Iterator.
Q2: Should I implement the language's built-in iterator protocol or define my own interface?
Always use the built-in protocol when possible. Python's __iter__/__next__, Java's Iterator<T>, JavaScript's Symbol.iterator, C#'s IEnumerable<T>, Swift's IteratorProtocol/Sequence, and Kotlin's Iterator<T>/Iterable<T> are all recognized by the language's for loop, spread operators, destructuring, and standard library functions. Custom interfaces only make sense for specialized traversal contracts (e.g., BiDirectionalIterator with hasPrevious()/previous()) that extend the standard protocol.
Q3: What is the difference between an external and internal iterator?
External iterator (pull-based): The client controls traversal by explicitly calling next(). More verbose but gives maximum control — you can pause, resume, read ahead, compare two iterators simultaneously, or exit early.
Internal iterator (push-based): The iterator controls traversal and calls a callback for each element (e.g., forEach((item) => process(item))). Simpler client code but less control — you can't easily pause mid-traversal or compare concurrent positions.
Most language for loops use external iterators under the hood. forEach, each, and reduce are internal iterators.
Q4: What is a lazy iterator and why does it matter?
A lazy iterator computes or fetches each element only when next() is called — not all at once upfront. Lazy iterators matter for:
- Infinite sequences:
fibonacci(),primes(),random_numbers()— these cannot be materialized - Large datasets: A database table with millions of rows should be streamed, not loaded into a List
- Early exit: If you only need the first matching element, a lazy iterator stops after finding it without processing the rest
- Memory efficiency: Processing 10GB of log files line by line uses constant memory; loading the whole file uses 10GB
Python's generators, JavaScript's generator functions, Rust's iterator adapters (.filter(), .map(), .take()), and Kotlin's Sequence are all lazy.
Q5: How do I implement a tree iterator that yields nodes in a specific order?
Traverse the tree iteratively using an explicit stack (for DFS) or queue (for BFS), rather than recursively — recursion depth is limited and recursive iterators are hard to pause:
class DepthFirstIterator:
def __init__(self, root):
self._stack = [root] if root else []
def __iter__(self): return self
def __next__(self):
if not self._stack:
raise StopIteration
node = self._stack.pop()
# Push children in reverse order so left child is processed first
self._stack.extend(reversed(node.children))
return node
class BreadthFirstIterator:
def __init__(self, root):
from collections import deque
self._queue = deque([root]) if root else deque()
def __iter__(self): return self
def __next__(self):
if not self._queue:
raise StopIteration
node = self._queue.popleft()
self._queue.extend(node.children)
return node
Q6: How do I safely iterate and modify a collection at the same time?
Never modify a collection while iterating it directly — most languages will throw a ConcurrentModificationException, a RuntimeError, or silently skip or repeat elements.
Safe approaches:
- Collect, then modify: Iterate to collect items to remove/modify, then apply changes after iteration is complete
- Iterator's own remove: Java's
Iterator.remove()is the only safe way to remove during iteration - Copy-then-iterate: Iterate a copy of the collection while modifying the original
- Build a new collection: Collect non-removed items into a new list instead of removing from the original
- Filter adapters: Rust's
retain(), Python's list comprehensions, and Kotlin'sfilter()create a new filtered collection safely
Q7: How do I implement a bidirectional iterator?
Extend the standard iterator interface with hasPrevious() and previous() methods:
interface BidirectionalIterator<T> extends Iterator<T> {
boolean hasPrevious();
T previous();
}
class BidirectionalBookIterator implements BidirectionalIterator<Book> {
private final List<Book> books;
private int pos;
BidirectionalBookIterator(List<Book> books, int startPos) {
this.books = books;
this.pos = startPos;
}
public boolean hasNext() { return pos < books.size(); }
public Book next() { return books.get(pos++); }
public boolean hasPrevious() { return pos > 0; }
public Book previous() { return books.get(--pos); }
}
Java's ListIterator is a bidirectional iterator built into the standard library.
Q8: When should I use an iterator vs. returning a collection?
Return a collection (List, Set, Array) when:
- The result is small and the caller will need random access (index-based lookup)
- The result will be iterated multiple times
- The caller needs
.size()or.length()upfront - Thread safety and snapshot semantics matter (a List is a snapshot; an iterator may see live data)
Return an iterator/stream/sequence when:
- The result could be very large or infinite
- The caller will likely exit early (first match, first N items)
- Generation is expensive and you want to defer computation
- You're building a pipeline where each stage transforms and passes lazily to the next
Q9: How do I combine (merge) multiple iterators into one?
Use the built-in chain/concat utilities or implement a MergedIterator:
import itertools
# Merge N iterators sequentially
combined = itertools.chain(iter1, iter2, iter3)
for item in combined: process(item)
# Round-robin merge (interleave)
for item in itertools.chain.from_iterable(zip(iter1, iter2)):
process(item)
function* chain<T>(...iterables: Iterable<T>[]): Generator<T> {
for (const iterable of iterables) yield* iterable;
}
Q10: What is the Iterator pattern's relationship to functional programming concepts like map, filter, and reduce?
map, filter, and reduce are higher-order operations on iterators:
mapreturns a transforming iterator that applies a function to each element lazilyfilterreturns a filtering iterator that skips non-matching elements lazilyreduce/foldis a terminal operation — it consumes the iterator and produces a single value
In Rust, these are zero-cost iterator adapters: .map(f).filter(p).fold(init, g) compiles to a single loop with no intermediate allocations. In Kotlin, Sequence.map().filter().first() is lazy — only processes items until the first match. Python generators compose the same way: next(filter(predicate, map(transform, source))) never materializes intermediate collections.
The Iterator Pattern is the foundation on which these functional operations are built — they're all just iterators wrapping other iterators.
Key Takeaways
🎯 Core Concept: The Iterator Pattern provides a standard interface to sequentially access elements of a collection without exposing the collection's internal structure. The iterator object carries the traversal state independently of the collection, enabling multiple simultaneous traversals of the same collection in different directions or with different filters.
🔑 Key Benefits:
- Decoupling: Algorithms written against the iterator interface work on arrays, trees, database cursors, network streams, and generated sequences without modification
- Multiple Independent Traversals: Multiple iterators on the same collection maintain independent positions — nested loops and parallel scans work correctly
- Lazy Evaluation: Fetch or compute elements on demand — process infinite sequences and large datasets with constant memory
- Language Integration: Implementing the built-in iterator protocol unlocks
forloops, spread, destructuring, and the entire standard library for free - Traversal Strategy Flexibility: Provide forward, reverse, filtered, tree-walk, and random iterators over the same collection without changing its interface
🌐 Language-Specific Highlights:
- Python: Implement both
__iter__(returnsselfon iterators, returns new iterator on collections) and__next__; never put traversal state on the collection itself; generators are the most idiomatic iterator implementation - TypeScript: Implement
[Symbol.iterator]()to unlockfor...of, spread[...shelf], and destructuringconst [first] = shelf; use generator functions (function*) for elegant lazy sequences - Java: Prefer the enhanced
for-eachloop (requiresIterable<T>); always callhasNext()beforenext(); useIterator.remove()(nevercollection.remove()) for safe removal during traversal; be aware ofConcurrentModificationException - JavaScript: Implement both
next()returning{ value, done }and[Symbol.iterator]()returningthis; generator functions (function*+yield) are the most idiomatic approach; neverfor...ina custom iterable (usefor...of) - C#: Implement
IEnumerable<T>+IEnumerator<T>pair;yield returnin iterator blocks handles all the state machinery automatically; alwaysusingyour enumerators to ensureDispose()is called, especially for DB cursors and file streams - PHP: Implement all 5 methods of
Iterator(current,key,next,rewind,valid); preferIteratorAggregatefor simple cases; never return$thisfromgetIterator() - Go: Use pointer receivers for iterators with mutable state; implement
HasNext()/Next()(Go has no built-in iterator protocol pre-1.23); userangewith channels for concurrent producer-consumer iteration; Go 1.23+ addsiter.Seq[V]as the standard iterator type - Rust: Understand
iter()(borrows, yields&T),iter_mut()(borrows mutably, yields&mut T), andinto_iter()(moves, yieldsT); usefor &book in &shelfto avoid move; iterator adapters (.map(),.filter(),.take()) are zero-cost abstractions — prefer them over manual loops - Dart: Extend
Iterable<T>for collections (givesforEach,map,where, etc. for free);Iterator<T>for the rawmoveNext()/currentprotocol; useyieldandyield*in generator functions;List.reversedand.where()return lazyIterables - Swift: Implement
IteratorProtocol(requiresmutating func next() -> Element?) for iterators; implementSequence(requiresmakeIterator()) for collections;structiterators requiremutatingonnext(); conforming toSequencegivesmap,filter,reduce,sorted, andcontainsfor free - Kotlin: Implement
Iterator<T>(requireshasNext()/next()) andIterable<T>(requiresiterator()) separately; usesequence {}builder withyieldfor lazy generators; preferSequenceoverListfor long lazy pipelines to avoid intermediate allocations;for-inrequiresIterable, notIterator
📋 When to Use Iterator:
- Traversal logic should be independent of collection structure (array, tree, graph, stream)
- Multiple simultaneous independent traversals of the same collection are needed
- You're working with large or infinite data sources that should be processed lazily
- You need pluggable traversal strategies (forward, reverse, filtered, breadth-first, depth-first)
- You want your custom collection to integrate with language
forloops and standard library functions - You're building a data pipeline where each stage lazily transforms and passes to the next
⚠️ When NOT to Use Iterator:
- Simple indexed array access with known size — a direct
for (int i = 0; i < n; i++)is clearer and faster - The collection is tiny (< 10 items) and performance of overhead matters — direct method calls are simpler
- Random access is needed (skip to index 500) — iterators are sequential; use the collection directly
- The traversal is inherently recursive and would require an explicit stack — recursive traversal may be cleaner
- All you need is a one-time
forEachormap— use the collection's built-in methods instead
🛠️ Best Practices Across Languages:
- Never put traversal state on the collection: Collections should be reusable; iterators carry the ephemeral position
- Implement the language's native protocol:
__iter__/__next__,Symbol.iterator,IEnumerable<T>,Sequence— always prefer native over custom interfaces for maximum interoperability - Return a new iterator from every
__iter__/iterator()call: Each call should produce a fresh, independent traversal - Make filtering iterators lazy: Don't materialize a filtered copy — skip non-matching elements inside
next() - Protect infinite iterators with
take(n)at the point of use: Never spread or collect an infinite iterator - Use
using/try-with-resources/deferfor resource-holding iterators: DB cursors, file iterators, and network streams must be closed - Prefer lazy sequences for long pipelines:
Sequence(Kotlin),itertoolschains (Python), Rust iterator adapters — avoid intermediate collections - Never modify a collection during external iteration: Collect targets first, then modify, or use the iterator's built-in
remove()
💡 Common Pitfalls:
- Collection IS its own iterator: Returning
self/this/$thisfrom__iter__/getIterator()— destroys nested loop correctness - Missing
__iter__on custom iterators: Python iterators must implement both__iter__(returningself) and__next__ - Exhausted generator reuse: Generators are one-shot — consuming one, then iterating again gives nothing
for...ininstead offor...of:for...inin JS/TS iterates over property keys;for...ofusesSymbol.iterator- Not disposing resource iterators: DB cursors and file iterators that aren't closed leak connections and file handles
- Modifying during iteration:
ConcurrentModificationException(Java),RuntimeError(Python), undefined behavior elsewhere - Rust
into_iter()consuming the collection: Use&collectionor.iter()to borrow non-destructively - Kotlin
SequencevsListconfusion:Sequenceis lazy and single-pass;Listis eager and reusable — mixing them incorrectly creates subtle bugs
🎁 Advanced Techniques:
- Cursor-based pagination: Store the last-seen ID as a cursor rather than a page number — more efficient for large, live datasets and avoids items appearing twice or being skipped when data is inserted/deleted mid-iteration
- Iterator fusion: Compilers (Rust, Haskell) and runtimes (Java Streams with spliterators) fuse adjacent
.map().filter()calls into a single loop pass — zero-overhead abstractions - Parallel iterators: Rust's Rayon, Java's parallel streams, and Kotlin's
asFlow().buffer()split iterators across threads — identical interface, parallel execution - Iterator coroutines: Kotlin
Flow, Python async generators (async def+yield), and JavaScript async generators (async function*) make iterators first-class async citizens —for await (const item of asyncIter) - Spliterators: Java's
Spliteratorextends Iterator withtrySplit()for parallel decomposition andestimateSize()for size hints — the foundation of Java parallel streams - Reactive streams: RxJava/RxJS bridge pull (Iterator) and push (Observer) by providing backpressure — the consumer signals demand, the producer emits at the requested rate
The Iterator Pattern is the single most universally implemented pattern in all of software — if you've ever written a for loop, you've used it. Understanding it deeply unlocks lazy evaluation, composable pipelines, and seamless integration with every language's standard library.
Want to dive deeper into design patterns? Check out our comprehensive guides on:
- Composite Pattern
- Observer Pattern
- Visitor Pattern
- Command Pattern
- Chain of Responsibility Pattern Each guide includes examples in all 11 programming languages with language-specific best practices.