Iterator Pattern
Iterator Pattern: Traversing Collections Uniformly
Section titled “Iterator Pattern: Traversing Collections Uniformly”Now let’s dive into the Iterator Pattern - one of the most fundamental behavioral design patterns that provides a way to access elements of a collection sequentially without exposing its underlying representation.
Why Iterator Pattern?
Section titled “Why Iterator Pattern?”Imagine you’re browsing a library. You don’t need to know how books are organized (by author, by topic, by shelf number) - you just walk through the aisles and browse. The library provides a consistent way to access books regardless of how they’re stored. The Iterator Pattern works the same way!
The Iterator Pattern provides a way to access elements of an aggregate object sequentially without exposing its underlying representation. It decouples the traversal logic from the collection structure.
What’s the Use of Iterator Pattern?
Section titled “What’s the Use of Iterator Pattern?”The Iterator Pattern is useful when:
- You want to traverse collections uniformly - Same interface for different collection types
- You want to hide collection structure - Clients don’t need to know internal representation
- You want multiple traversals - Support multiple simultaneous traversals
- You want to decouple traversal logic - Separate iteration logic from collection
- You want to support different iteration strategies - Forward, backward, filtered, etc.
What Happens If We Don’t Use Iterator Pattern?
Section titled “What Happens If We Don’t Use Iterator Pattern?”Without the Iterator Pattern, you might:
- Expose internal structure - Clients need to know how collection is implemented
- Tight coupling - Clients depend on specific collection implementation
- Code duplication - Traversal logic repeated for each collection type
- Hard to change - Changing collection structure breaks client code
- No uniform interface - Different collections have different traversal methods
Simple Example: The Book Collection
Section titled “Simple Example: The Book Collection”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Iterator Pattern works in practice - showing how iterator traverses collections:
The Problem
Section titled “The Problem”You’re building a library system with different collection types (array, linked list). Without Iterator Pattern:
# ❌ Without Iterator Pattern - Exposed internal structure!
class Book: """Book model""" def __init__(self, title: str): self.title = title
def __str__(self): return self.title
class BookArray: """Book collection using array""" def __init__(self): self.books = [] # Array implementation
def add(self, book: Book): self.books.append(book)
def get(self, index: int) -> Book: return self.books[index]
def size(self) -> int: return len(self.books)
class BookLinkedList: """Book collection using linked list""" class Node: def __init__(self, book: Book): self.book = book self.next = None
def __init__(self): self.head = None # Linked list implementation
def add(self, book: Book): node = self.Node(book) node.next = self.head self.head = node
def get_first(self) -> Book: return self.head.book if self.head else None
# Problem: Client needs to know internal structure!def traverse_array(collection: BookArray): # Client knows it's an array - uses index for i in range(collection.size()): book = collection.get(i) print(f"Reading: {book}")
def traverse_linked_list(collection: BookLinkedList): # Client knows it's a linked list - uses node traversal current = collection.head while current: print(f"Reading: {current.book}") current = current.next
# Problems:# - Client needs to know collection type# - Different traversal code for each type# - Tight coupling to implementation# - Hard to add new collection types// ❌ Without Iterator Pattern - Exposed internal structure!
class Book { // Book model private String title;
public Book(String title) { this.title = title; }
@Override public String toString() { return title; }}
class BookArray { // Book collection using array private List<Book> books = new ArrayList<>(); // Array implementation
public void add(Book book) { books.add(book); }
public Book get(int index) { return books.get(index); }
public int size() { return books.size(); }}
class BookLinkedList { // Book collection using linked list class Node { Book book; Node next;
Node(Book book) { this.book = book; } }
private Node head; // Linked list implementation
public void add(Book book) { Node node = new Node(book); node.next = head; head = node; }
public Book getFirst() { return head != null ? head.book : null; }}
// Problem: Client needs to know internal structure!public static void traverseArray(BookArray collection) { // Client knows it's an array - uses index for (int i = 0; i < collection.size(); i++) { Book book = collection.get(i); System.out.println("Reading: " + book); }}
public static void traverseLinkedList(BookLinkedList collection) { // Client knows it's a linked list - uses node traversal BookLinkedList.Node current = collection.head; while (current != null) { System.out.println("Reading: " + current.book); current = current.next; }}
// Problems:// - Client needs to know collection type// - Different traversal code for each type// - Tight coupling to implementation// - Hard to add new collection types// ❌ Without Iterator Pattern - Exposed internal structure!
class Book { /** Book model */ title: string;
constructor(title: string) { this.title = title; }
toString(): string { return this.title; }}
class BookArray { /** Book collection using array */ private books: Book[] = []; // Array implementation
add(book: Book): void { this.books.push(book); }
get(index: number): Book { return this.books[index]; }
size(): number { return this.books.length; }}
class BookLinkedList { /** Book collection using linked list */ private head: Node | null = null; // Linked list implementation
add(book: Book): void { const node = new Node(book); node.next = this.head; this.head = node; }
getFirst(): Book | null { return this.head ? this.head.book : null; }
getHead(): Node | null { return this.head; }}
class Node { book: Book; next: Node | null = null;
constructor(book: Book) { this.book = book; }}
// Problem: Client needs to know internal structure!function traverseArray(collection: BookArray): void { // Client knows it's an array - uses index for (let i = 0; i < collection.size(); i++) { const book = collection.get(i); console.log(`Reading: ${book}`); }}
function traverseLinkedList(collection: BookLinkedList): void { // Client knows it's a linked list - uses node traversal let current = collection.getHead(); while (current !== null) { console.log(`Reading: ${current.book}`); current = current.next; }}
// Problems:// - Client needs to know collection type// - Different traversal code for each type// - Tight coupling to implementation// - Hard to add new collection types// ❌ Without Iterator Pattern - Exposed internal structure!
#include <iostream>#include <vector>#include <string>
class Book {private: std::string title;
public: Book(const std::string& title) : title(title) {}
std::string toString() const { return title; }};
class BookArray {private: std::vector<Book> books; // Array implementation
public: void add(const Book& book) { books.push_back(book); }
Book get(int index) const { return books[index]; }
int size() const { return books.size(); }};
class BookLinkedList {public: struct Node { Book book; Node* next;
Node(const Book& b) : book(b), next(nullptr) {} };
private: Node* head; // Linked list implementation
public: BookLinkedList() : head(nullptr) {}
~BookLinkedList() { while (head) { Node* temp = head; head = head->next; delete temp; } }
void add(const Book& book) { Node* node = new Node(book); node->next = head; head = node; }
Book* getFirst() { return head ? &head->book : nullptr; }
Node* getHead() const { return head; }};
// Problem: Client needs to know internal structure!void traverseArray(const BookArray& collection) { // Client knows it's an array - uses index for (int i = 0; i < collection.size(); i++) { Book book = collection.get(i); std::cout << "Reading: " << book.toString() << std::endl; }}
void traverseLinkedList(const BookLinkedList& collection) { // Client knows it's a linked list - uses node traversal BookLinkedList::Node* current = collection.getHead(); while (current) { std::cout << "Reading: " << current->book.toString() << std::endl; current = current->next; }}
// Problems:// - Client needs to know collection type// - Different traversal code for each type// - Tight coupling to implementation// - Hard to add new collection types// ❌ Without Iterator Pattern - Exposed internal structure!
using System;using System.Collections.Generic;
public class Book{ /** Book model */ private string title;
public Book(string title) { this.title = title; }
public override string ToString() { return title; }}
public class BookArray{ /** Book collection using array */ private List<Book> books = new List<Book>(); // Array implementation
public void Add(Book book) { books.Add(book); }
public Book Get(int index) { return books[index]; }
public int Size() { return books.Count; }}
public class BookLinkedList{ /** Book collection using linked list */ public class Node { public Book book; public Node next;
public Node(Book book) { this.book = book; } }
public Node head; // Linked list implementation
public void Add(Book book) { Node node = new Node(book); node.next = head; head = node; }
public Book GetFirst() { return head != null ? head.book : null; }}
// Problem: Client needs to know internal structure!class Program{ static void TraverseArray(BookArray collection) { // Client knows it's an array - uses index for (int i = 0; i < collection.Size(); i++) { Book book = collection.Get(i); Console.WriteLine($"Reading: {book}"); } }
static void TraverseLinkedList(BookLinkedList collection) { // Client knows it's a linked list - uses node traversal BookLinkedList.Node current = collection.head; while (current != null) { Console.WriteLine($"Reading: {current.book}"); current = current.next; } }}
// Problems:// - Client needs to know collection type// - Different traversal code for each type// - Tight coupling to implementation// - Hard to add new collection typespackage main
import "fmt"
// ❌ Without Iterator Pattern - Exposed internal structure!
type Book struct { title string}
func (b Book) String() string { return b.title }
type BookSlice struct { books []Book}
func (bs *BookSlice) Add(book Book) { bs.books = append(bs.books, book) }func (bs *BookSlice) Get(i int) Book { return bs.books[i] }func (bs *BookSlice) Size() int { return len(bs.books) }
// Problem: Client needs to know internal structure!func traverseSlice(bs *BookSlice) { for i := 0; i < bs.Size(); i++ { fmt.Println("Reading:", bs.Get(i)) }}
// Problems:// - Client knows it's a slice - uses index// - Hard to add new collection types// - Tight coupling to implementation// The Problemstruct Playlist { songs: Vec<String>,}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; // Client depends on Vec internals. for i in 0..playlist.songs.len() { println!("{}", playlist.songs[i]); }}Problems:
- Exposed internal structure - Clients need to know implementation details
- Different traversal code - Each collection type requires different code
- Tight coupling - Clients depend on specific implementations
- Hard to extend - Adding new collection types breaks client code
The Solution: Iterator Pattern
Section titled “The Solution: Iterator Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethodfrom typing import List, Optional
# Step 1: Define Iterator interfaceclass Iterator(ABC): """Iterator interface - uniform traversal interface"""
@abstractmethod def has_next(self) -> bool: """Check if there are more elements""" pass
@abstractmethod def next(self): """Get next element and advance""" pass
@abstractmethod def current(self): """Get current element without advancing""" pass
# Step 2: Define Iterable interfaceclass Iterable(ABC): """Iterable interface - collections that can be iterated"""
@abstractmethod def create_iterator(self) -> Iterator: """Create an iterator for this collection""" pass
# Step 3: Implement Book modelclass Book: """Book model""" def __init__(self, title: str): self.title = title
def __str__(self): return self.title
# Step 4: Implement Concrete Aggregateclass BookCollection(Iterable): """Concrete aggregate - book collection"""
def __init__(self): self._books: List[Book] = [] # Internal structure hidden
def add(self, book: Book) -> None: """Add a book to collection""" self._books.append(book)
def get(self, index: int) -> Book: """Get book by index (internal method)""" return self._books[index]
def size(self) -> int: """Get collection size (internal method)""" return len(self._books)
def create_iterator(self) -> Iterator: """Create iterator for this collection""" return BookIterator(self)
# Step 5: Implement Concrete Iteratorclass BookIterator(Iterator): """Concrete iterator - traverses book collection"""
def __init__(self, collection: BookCollection): self._collection = collection self._index = 0 # Current position
def has_next(self) -> bool: """Check if there are more books""" return self._index < self._collection.size()
def next(self) -> Book: """Get next book and advance""" if not self.has_next(): raise StopIteration("No more books")
book = self._collection.get(self._index) self._index += 1 return book
def current(self) -> Book: """Get current book without advancing""" if self._index >= self._collection.size(): raise IndexError("No current book") return self._collection.get(self._index)
# Usage - Uniform interface!def main(): # Create collection collection = BookCollection() collection.add(Book("Design Patterns")) collection.add(Book("Clean Code")) collection.add(Book("Refactoring"))
# Client uses uniform iterator interface - doesn't know internal structure! iterator = collection.create_iterator()
print("Traversing books:\n") while iterator.has_next(): book = iterator.next() print(f"Reading: {book}")
print("\n✅ Iterator Pattern: Uniform interface for all collections!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define Iterator interfaceinterface Iterator<T> { // Iterator interface - uniform traversal interface boolean hasNext(); T next(); T current();}
// Step 2: Define Iterable interfaceinterface Iterable<T> { // Iterable interface - collections that can be iterated Iterator<T> createIterator();}
// Step 3: Implement Book modelclass Book { // Book model private String title;
public Book(String title) { this.title = title; }
@Override public String toString() { return title; }}
// Step 4: Implement Concrete Aggregateclass BookCollection implements Iterable<Book> { // Concrete aggregate - book collection private List<Book> books; // Internal structure hidden
public BookCollection() { this.books = new ArrayList<>(); }
public void add(Book book) { // Add a book to collection books.add(book); }
Book get(int index) { // Get book by index (internal method) return books.get(index); }
int size() { // Get collection size (internal method) return books.size(); }
@Override public Iterator<Book> createIterator() { // Create iterator for this collection return new BookIterator(this); }}
// Step 5: Implement Concrete Iteratorclass BookIterator implements Iterator<Book> { // Concrete iterator - traverses book collection private BookCollection collection; private int index; // Current position
public BookIterator(BookCollection collection) { this.collection = collection; this.index = 0; }
@Override public boolean hasNext() { // Check if there are more books return index < collection.size(); }
@Override public Book next() { // Get next book and advance if (!hasNext()) { throw new NoSuchElementException("No more books"); }
Book book = collection.get(index); index++; return book; }
@Override public Book current() { // Get current book without advancing if (index >= collection.size()) { throw new IndexOutOfBoundsException("No current book"); } return collection.get(index); }}
// Usage - Uniform interface!public class Main { public static void main(String[] args) { // Create collection BookCollection collection = new BookCollection(); collection.add(new Book("Design Patterns")); collection.add(new Book("Clean Code")); collection.add(new Book("Refactoring"));
// Client uses uniform iterator interface - doesn't know internal structure! Iterator<Book> iterator = collection.createIterator();
System.out.println("Traversing books:\n"); while (iterator.hasNext()) { Book book = iterator.next(); System.out.println("Reading: " + book); }
System.out.println("\n✅ Iterator Pattern: Uniform interface for all collections!"); }}// Step 1: Define Iterator interfaceinterface Iterator<T> { /** Iterator interface - uniform traversal interface */ hasNext(): boolean; next(): T; current(): T | null;}
// Step 2: Define Iterable interfaceinterface Iterable<T> { /** Iterable interface - collections that can be iterated */ createIterator(): Iterator<T>;}
// Step 3: Implement Book modelclass Book { /** Book model */ constructor(public title: string) {}
toString(): string { return this.title; }}
// Step 4: Implement Concrete Aggregateclass BookCollection implements Iterable<Book> { /** Concrete aggregate - book collection */ private books: Book[] = []; // Internal structure hidden
add(book: Book): void { /** Add a book to collection */ this.books.push(book); }
get(index: number): Book { /** Get book by index (internal method) */ return this.books[index]; }
size(): number { /** Get collection size (internal method) */ return this.books.length; }
createIterator(): Iterator<Book> { /** Create iterator for this collection */ return new BookIterator(this); }}
// Step 5: Implement Concrete Iteratorclass BookIterator implements Iterator<Book> { /** Concrete iterator - traverses book collection */ private collection: BookCollection; private index: number = 0; // Current position
constructor(collection: BookCollection) { this.collection = collection; }
hasNext(): boolean { /** Check if there are more books */ return this.index < this.collection.size(); }
next(): Book { /** Get next book and advance */ if (!this.hasNext()) { throw new Error("No more books"); }
const book = this.collection.get(this.index); this.index++; return book; }
current(): Book | null { /** Get current book without advancing */ if (this.index < this.collection.size()) { return this.collection.get(this.index); } return null; }}
// Usage - Uniform interface!function main(): void { // Create collection const collection = new BookCollection(); collection.add(new Book("Design Patterns")); collection.add(new Book("Clean Code")); collection.add(new Book("Refactoring"));
// Client uses uniform iterator interface - doesn't know internal structure! const iterator = collection.createIterator();
console.log("Traversing books:\n"); while (iterator.hasNext()) { const book = iterator.next(); console.log(`Reading: ${book}`); }
console.log("\n✅ Iterator Pattern: Uniform interface for all collections!");}
main();#include <iostream>#include <vector>#include <string>#include <memory>#include <stdexcept>
// Forward declarationsclass Book;template<typename T> class Iterator;template<typename T> class Iterable;
// Step 3: Implement Book modelclass Book {private: std::string title;
public: Book(const std::string& title) : title(title) {}
std::string toString() const { return title; }};
// Step 1: Define Iterator interfacetemplate<typename T>class Iterator {public: virtual ~Iterator() = default; // Iterator interface - uniform traversal interface virtual bool hasNext() = 0; virtual T next() = 0; virtual T* current() = 0;};
// Step 2: Define Iterable interfacetemplate<typename T>class Iterable {public: virtual ~Iterable() = default; // Iterable interface - collections that can be iterated virtual std::unique_ptr<Iterator<T>> createIterator() = 0;};
// Forward declarationclass BookIterator;
// Step 4: Implement Concrete Aggregateclass BookCollection : public Iterable<Book> {private: std::vector<Book> books; // Internal structure hidden
public: void add(const Book& book) { // Add a book to collection books.push_back(book); }
Book get(int index) const { // Get book by index (internal method) return books[index]; }
int size() const { // Get collection size (internal method) return books.size(); }
std::unique_ptr<Iterator<Book>> createIterator() override;};
// Step 5: Implement Concrete Iteratorclass BookIterator : public Iterator<Book> {private: const BookCollection* collection; int index; // Current position
public: BookIterator(const BookCollection* collection) : collection(collection), index(0) {}
bool hasNext() override { // Check if there are more books return index < collection->size(); }
Book next() override { // Get next book and advance if (!hasNext()) { throw std::runtime_error("No more books"); }
Book book = collection->get(index); index++; return book; }
Book* current() override { // Get current book without advancing if (index < collection->size()) { // Note: returning pointer to temporary - in real code, store book static Book currentBook = collection->get(index); currentBook = collection->get(index); return ¤tBook; } return nullptr; }};
std::unique_ptr<Iterator<Book>> BookCollection::createIterator() { return std::make_unique<BookIterator>(this);}
// Usage - Uniform interface!int main() { // Create collection BookCollection collection; collection.add(Book("Design Patterns")); collection.add(Book("Clean Code")); collection.add(Book("Refactoring"));
// Client uses uniform iterator interface - doesn't know internal structure! auto iterator = collection.createIterator();
std::cout << "Traversing books:\n" << std::endl; while (iterator->hasNext()) { Book book = iterator->next(); std::cout << "Reading: " << book.toString() << std::endl; }
std::cout << "\n✅ Iterator Pattern: Uniform interface for all collections!" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define Iterator interfacepublic interface IIterator<T>{ /** Iterator interface - uniform traversal interface */ bool HasNext(); T Next(); T Current();}
// Step 2: Define Iterable interfacepublic interface IIterable<T>{ /** Iterable interface - collections that can be iterated */ IIterator<T> CreateIterator();}
// Step 3: Implement Book modelpublic class Book{ /** Book model */ private string title;
public Book(string title) { this.title = title; }
public override string ToString() { return title; }}
// Step 4: Implement Concrete Aggregatepublic class BookCollection : IIterable<Book>{ /** Concrete aggregate - book collection */ private List<Book> books = new List<Book>(); // Internal structure hidden
public void Add(Book book) { /** Add a book to collection */ books.Add(book); }
internal Book Get(int index) { /** Get book by index (internal method) */ return books[index]; }
internal int Size() { /** Get collection size (internal method) */ return books.Count; }
public IIterator<Book> CreateIterator() { /** Create iterator for this collection */ return new BookIterator(this); }}
// Step 5: Implement Concrete Iteratorpublic class BookIterator : IIterator<Book>{ /** Concrete iterator - traverses book collection */ private BookCollection collection; private int index = 0; // Current position
public BookIterator(BookCollection collection) { this.collection = collection; }
public bool HasNext() { /** Check if there are more books */ return index < collection.Size(); }
public Book Next() { /** Get next book and advance */ if (!HasNext()) { throw new InvalidOperationException("No more books"); }
Book book = collection.Get(index); index++; return book; }
public Book Current() { /** Get current book without advancing */ if (index < collection.Size()) { return collection.Get(index); } return null; }}
// Usage - Uniform interface!class Program{ static void Main() { // Create collection BookCollection collection = new BookCollection(); collection.Add(new Book("Design Patterns")); collection.Add(new Book("Clean Code")); collection.Add(new Book("Refactoring"));
// Client uses uniform iterator interface - doesn't know internal structure! IIterator<Book> iterator = collection.CreateIterator();
Console.WriteLine("Traversing books:\n"); while (iterator.HasNext()) { Book book = iterator.Next(); Console.WriteLine($"Reading: {book}"); }
Console.WriteLine("\n✅ Iterator Pattern: Uniform interface for all collections!"); }}package main
import "fmt"
// Step 1: Iterator interfacetype BookIteratorI interface { HasNext() bool Next() Book Current() Book}
// Step 2: Iterable interfacetype Iterable interface { CreateIterator() BookIteratorI}
// Step 3: Book modeltype Book struct{ title string }
func (b Book) String() string { return b.title }
// Step 4: Concrete Aggregatetype BookCollection struct{ books []Book }
func (c *BookCollection) Add(book Book) { c.books = append(c.books, book) }func (c *BookCollection) Size() int { return len(c.books) }func (c *BookCollection) Get(i int) Book { return c.books[i] }func (c *BookCollection) CreateIterator() BookIteratorI { return &BookIterator{collection: c, index: 0}}
// Step 5: Concrete Iteratortype BookIterator struct { collection *BookCollection index int}
func (it *BookIterator) HasNext() bool { return it.index < it.collection.Size() }func (it *BookIterator) Next() Book { book := it.collection.Get(it.index) it.index++ return book}func (it *BookIterator) Current() Book { return it.collection.Get(it.index) }
func main() { collection := &BookCollection{} collection.Add(Book{"Design Patterns"}) collection.Add(Book{"Clean Code"}) collection.Add(Book{"Refactoring"})
iterator := collection.CreateIterator() fmt.Println("Traversing books:") for iterator.HasNext() { book := iterator.Next() fmt.Println("Reading:", book) } fmt.Println("\n✅ Iterator Pattern: Uniform interface for all collections!")}// Class Structurestruct Playlist { songs: Vec<String>,}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; // Client depends on Vec internals. for i in 0..playlist.songs.len() { println!("{}", playlist.songs[i]); }}Real-World Software Example: Distributed Data Processing
Section titled “Real-World Software Example: Distributed Data Processing”Now let’s see a realistic software example - a distributed system that needs to process data from multiple sources (database, file system, API) uniformly.
The Problem
Section titled “The Problem”You’re building a data processing pipeline that needs to process records from different sources. Without Iterator Pattern:
# ❌ Without Iterator Pattern - Different access patterns!
class DatabaseSource: """Database data source""" def __init__(self, connection_string: str): self.connection_string = connection_string # Simulate database connection self.records = [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Charlie"} ] self.current_index = 0
def fetch_all(self): """Fetch all records""" return self.records
def fetch_next(self): """Fetch next record""" if self.current_index < len(self.records): record = self.records[self.current_index] self.current_index += 1 return record return None
class FileSource: """File data source""" def __init__(self, filepath: str): self.filepath = filepath # Simulate file reading self.lines = [ "id,name", "1,Alice", "2,Bob", "3,Charlie" ] self.current_line = 0
def read_lines(self): """Read all lines""" return self.lines[1:] # Skip header
def read_next_line(self): """Read next line""" if self.current_line < len(self.lines) - 1: line = self.lines[self.current_line + 1] self.current_line += 1 return line return None
class APISource: """API data source""" def __init__(self, endpoint: str): self.endpoint = endpoint # Simulate API response self.data = { "records": [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, {"id": 3, "name": "Charlie"} ] }
def get_all(self): """Get all records""" return self.data["records"]
def get_next_page(self): """Get next page (pagination)""" return self.data["records"]
# Problem: Client needs different code for each source!def process_database(source: DatabaseSource): records = source.fetch_all() # Different method! for record in records: print(f"Processing: {record}")
def process_file(source: FileSource): lines = source.read_lines() # Different method! for line in lines: print(f"Processing: {line}")
def process_api(source: APISource): records = source.get_all() # Different method! for record in records: print(f"Processing: {record}")
# Problems:# - Different access patterns for each source# - Client needs to know source type# - Hard to add new sources# - Code duplication// ❌ Without Iterator Pattern - Different access patterns!
class DatabaseSource { // Database data source private String connectionString; private List<Map<String, Object>> records; private int currentIndex;
public DatabaseSource(String connectionString) { this.connectionString = connectionString; // Simulate database connection this.records = new ArrayList<>(); records.add(Map.of("id", 1, "name", "Alice")); records.add(Map.of("id", 2, "name", "Bob")); records.add(Map.of("id", 3, "name", "Charlie")); this.currentIndex = 0; }
public List<Map<String, Object>> fetchAll() { // Fetch all records return records; }
public Map<String, Object> fetchNext() { // Fetch next record if (currentIndex < records.size()) { return records.get(currentIndex++); } return null; }}
class FileSource { // File data source private String filepath; private List<String> lines; private int currentLine;
public FileSource(String filepath) { this.filepath = filepath; // Simulate file reading this.lines = Arrays.asList("id,name", "1,Alice", "2,Bob", "3,Charlie"); this.currentLine = 0; }
public List<String> readLines() { // Read all lines return lines.subList(1, lines.size()); // Skip header }
public String readNextLine() { // Read next line if (currentLine < lines.size() - 1) { return lines.get(++currentLine); } return null; }}
class APISource { // API data source private String endpoint; private Map<String, List<Map<String, Object>>> data;
public APISource(String endpoint) { this.endpoint = endpoint; // Simulate API response this.data = Map.of("records", Arrays.asList( Map.of("id", 1, "name", "Alice"), Map.of("id", 2, "name", "Bob"), Map.of("id", 3, "name", "Charlie") )); }
public List<Map<String, Object>> getAll() { // Get all records return data.get("records"); }}
// Problem: Client needs different code for each source!public static void processDatabase(DatabaseSource source) { List<Map<String, Object>> records = source.fetchAll(); // Different method! for (Map<String, Object> record : records) { System.out.println("Processing: " + record); }}
public static void processFile(FileSource source) { List<String> lines = source.readLines(); // Different method! for (String line : lines) { System.out.println("Processing: " + line); }}
public static void processAPI(APISource source) { List<Map<String, Object>> records = source.getAll(); // Different method! for (Map<String, Object> record : records) { System.out.println("Processing: " + record); }}
// Problems:// - Different access patterns for each source// - Client needs to know source type// - Hard to add new sources// - Code duplication// ❌ Without Iterator Pattern - Different access patterns!
class DatabaseSource { /** Database data source */ private connectionString: string; private records: Record<string, any>[]; private currentIndex: number = 0;
constructor(connectionString: string) { this.connectionString = connectionString; // Simulate database connection this.records = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, { id: 3, name: "Charlie" } ]; }
fetchAll(): Record<string, any>[] { // Fetch all records return this.records; }
fetchNext(): Record<string, any> | null { // Fetch next record if (this.currentIndex < this.records.length) { return this.records[this.currentIndex++]; } return null; }}
class FileSource { /** File data source */ private filepath: string; private lines: string[]; private currentLine: number = 0;
constructor(filepath: string) { this.filepath = filepath; // Simulate file reading this.lines = ["id,name", "1,Alice", "2,Bob", "3,Charlie"]; }
readLines(): string[] { // Read all lines return this.lines.slice(1); // Skip header }
readNextLine(): string | null { // Read next line if (this.currentLine < this.lines.length - 1) { return this.lines[++this.currentLine]; } return null; }}
class APISource { /** API data source */ private endpoint: string; private data: { records: Record<string, any>[] };
constructor(endpoint: string) { this.endpoint = endpoint; // Simulate API response this.data = { records: [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, { id: 3, name: "Charlie" } ] }; }
getAll(): Record<string, any>[] { // Get all records return this.data.records; }}
// Problem: Client needs different code for each source!function processDatabase(source: DatabaseSource): void { const records = source.fetchAll(); // Different method! for (const record of records) { console.log(`Processing: ${JSON.stringify(record)}`); }}
function processFile(source: FileSource): void { const lines = source.readLines(); // Different method! for (const line of lines) { console.log(`Processing: ${line}`); }}
function processAPI(source: APISource): void { const records = source.getAll(); // Different method! for (const record of records) { console.log(`Processing: ${JSON.stringify(record)}`); }}
// Problems:// - Different access patterns for each source// - Client needs to know source type// - Hard to add new sources// - Code duplication// ❌ Without Iterator Pattern - Different access patterns!
#include <iostream>#include <vector>#include <map>#include <string>
class DatabaseSource {private: std::string connectionString; std::vector<std::map<std::string, std::string>> records; int currentIndex;
public: DatabaseSource(const std::string& connectionString) : connectionString(connectionString), currentIndex(0) { // Simulate database connection records.push_back({{"id", "1"}, {"name", "Alice"}}); records.push_back({{"id", "2"}, {"name", "Bob"}}); records.push_back({{"id", "3"}, {"name", "Charlie"}}); }
std::vector<std::map<std::string, std::string>> fetchAll() { // Fetch all records return records; }
std::map<std::string, std::string>* fetchNext() { // Fetch next record if (currentIndex < records.size()) { return &records[currentIndex++]; } return nullptr; }};
class FileSource {private: std::string filepath; std::vector<std::string> lines; int currentLine;
public: FileSource(const std::string& filepath) : filepath(filepath), currentLine(0) { // Simulate file reading lines = {"id,name", "1,Alice", "2,Bob", "3,Charlie"}; }
std::vector<std::string> readLines() { // Read all lines std::vector<std::string> result(lines.begin() + 1, lines.end()); return result; }
std::string* readNextLine() { // Read next line if (currentLine < lines.size() - 1) { return &lines[++currentLine]; } return nullptr; }};
class APISource {private: std::string endpoint; std::vector<std::map<std::string, std::string>> records;
public: APISource(const std::string& endpoint) : endpoint(endpoint) { // Simulate API response records.push_back({{"id", "1"}, {"name", "Alice"}}); records.push_back({{"id", "2"}, {"name", "Bob"}}); records.push_back({{"id", "3"}, {"name", "Charlie"}}); }
std::vector<std::map<std::string, std::string>> getAll() { // Get all records return records; }};
// Problem: Client needs different code for each source!void processDatabase(DatabaseSource& source) { auto records = source.fetchAll(); // Different method! for (const auto& record : records) { std::cout << "Processing: {id: " << record.at("id") << ", name: " << record.at("name") << "}" << std::endl; }}
void processFile(FileSource& source) { auto lines = source.readLines(); // Different method! for (const auto& line : lines) { std::cout << "Processing: " << line << std::endl; }}
void processAPI(APISource& source) { auto records = source.getAll(); // Different method! for (const auto& record : records) { std::cout << "Processing: {id: " << record.at("id") << ", name: " << record.at("name") << "}" << std::endl; }}
// Problems:// - Different access patterns for each source// - Client needs to know source type// - Hard to add new sources// - Code duplication// ❌ Without Iterator Pattern - Different access patterns!
using System;using System.Collections.Generic;
public class DatabaseSource{ /** Database data source */ private string connectionString; private List<Dictionary<string, object>> records; private int currentIndex = 0;
public DatabaseSource(string connectionString) { this.connectionString = connectionString; // Simulate database connection this.records = new List<Dictionary<string, object>> { new Dictionary<string, object> { ["id"] = 1, ["name"] = "Alice" }, new Dictionary<string, object> { ["id"] = 2, ["name"] = "Bob" }, new Dictionary<string, object> { ["id"] = 3, ["name"] = "Charlie" } }; }
public List<Dictionary<string, object>> FetchAll() { // Fetch all records return records; }
public Dictionary<string, object> FetchNext() { // Fetch next record if (currentIndex < records.Count) { return records[currentIndex++]; } return null; }}
public class FileSource{ /** File data source */ private string filepath; private List<string> lines; private int currentLine = 0;
public FileSource(string filepath) { this.filepath = filepath; // Simulate file reading this.lines = new List<string> { "id,name", "1,Alice", "2,Bob", "3,Charlie" }; }
public List<string> ReadLines() { // Read all lines return lines.GetRange(1, lines.Count - 1); // Skip header }
public string ReadNextLine() { // Read next line if (currentLine < lines.Count - 1) { return lines[++currentLine]; } return null; }}
public class APISource{ /** API data source */ private string endpoint; private Dictionary<string, List<Dictionary<string, object>>> data;
public APISource(string endpoint) { this.endpoint = endpoint; // Simulate API response this.data = new Dictionary<string, List<Dictionary<string, object>>> { ["records"] = new List<Dictionary<string, object>> { new Dictionary<string, object> { ["id"] = 1, ["name"] = "Alice" }, new Dictionary<string, object> { ["id"] = 2, ["name"] = "Bob" }, new Dictionary<string, object> { ["id"] = 3, ["name"] = "Charlie" } } }; }
public List<Dictionary<string, object>> GetAll() { // Get all records return data["records"]; }}
// Problem: Client needs different code for each source!class Program{ static void ProcessDatabase(DatabaseSource source) { var records = source.FetchAll(); // Different method! foreach (var record in records) { Console.WriteLine($"Processing: {record}"); } }
static void ProcessFile(FileSource source) { var lines = source.ReadLines(); // Different method! foreach (var line in lines) { Console.WriteLine($"Processing: {line}"); } }
static void ProcessAPI(APISource source) { var records = source.GetAll(); // Different method! foreach (var record in records) { Console.WriteLine($"Processing: {record}"); } }}
// Problems:// - Different access patterns for each source// - Client needs to know source type// - Hard to add new sources// - Code duplicationpackage main
import "fmt"
// ❌ Without Iterator Pattern - Different access patterns!
type DatabaseSource struct { records []map[string]string index int}
func NewDatabaseSource() *DatabaseSource { return &DatabaseSource{records: []map[string]string{ {"id": "1", "name": "Alice"}, {"id": "2", "name": "Bob"}, {"id": "3", "name": "Charlie"}, }}}func (d *DatabaseSource) FetchAll() []map[string]string { return d.records }
type FileSource struct{ lines []string }
func NewFileSource() *FileSource { return &FileSource{lines: []string{"id,name", "1,Alice", "2,Bob", "3,Charlie"}}}func (f *FileSource) ReadLines() []string { return f.lines[1:] }
// Problem: Client needs different code for each source!func processDatabase(s *DatabaseSource) { for _, r := range s.FetchAll() { // Different method! fmt.Println("Processing:", r) }}func processFile(f *FileSource) { for _, l := range f.ReadLines() { // Different method! fmt.Println("Processing:", l) }}// Problems: Different access patterns, tight coupling, hard to extend// The Problemstruct Playlist { songs: Vec<String>,}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; // Client depends on Vec internals. for i in 0..playlist.songs.len() { println!("{}", playlist.songs[i]); }}Problems:
- Different access patterns - Each source has different methods
- Client needs source type - Must know which source it’s using
- Hard to extend - Adding new sources requires client changes
- Code duplication - Similar processing logic repeated
The Solution: Iterator Pattern
Section titled “The Solution: Iterator Pattern”from abc import ABC, abstractmethodfrom typing import Dict, Any, Optional
# Step 1: Define Iterator interfaceclass DataIterator(ABC): """Iterator interface for data sources"""
@abstractmethod def has_next(self) -> bool: """Check if there are more records""" pass
@abstractmethod def next(self) -> Dict[str, Any]: """Get next record""" pass
# Step 2: Define Iterable interfaceclass DataSource(ABC): """Iterable interface for data sources"""
@abstractmethod def create_iterator(self) -> DataIterator: """Create iterator for this data source""" pass
# Step 3: Implement Database Source with Iteratorclass DatabaseSource(DataSource): """Database data source"""
def __init__(self, connection_string: str): self.connection_string = connection_string # Simulate database records self._records = [ {"id": 1, "name": "Alice", "source": "database"}, {"id": 2, "name": "Bob", "source": "database"}, {"id": 3, "name": "Charlie", "source": "database"} ]
def create_iterator(self) -> DataIterator: """Create database iterator""" return DatabaseIterator(self)
def get_records(self): """Internal method to get records""" return self._records
class DatabaseIterator(DataIterator): """Iterator for database source"""
def __init__(self, source: DatabaseSource): self._source = source self._records = source.get_records() self._index = 0
def has_next(self) -> bool: return self._index < len(self._records)
def next(self) -> Dict[str, Any]: if not self.has_next(): raise StopIteration("No more records") record = self._records[self._index] self._index += 1 return record
# Step 4: Implement File Source with Iteratorclass FileSource(DataSource): """File data source"""
def __init__(self, filepath: str): self.filepath = filepath # Simulate file lines self._lines = [ "id,name", "1,Alice", "2,Bob", "3,Charlie" ]
def create_iterator(self) -> DataIterator: """Create file iterator""" return FileIterator(self)
def get_lines(self): """Internal method to get lines""" return self._lines
class FileIterator(DataIterator): """Iterator for file source"""
def __init__(self, source: FileSource): self._source = source self._lines = source.get_lines()[1:] # Skip header self._index = 0
def has_next(self) -> bool: return self._index < len(self._lines)
def next(self) -> Dict[str, Any]: if not self.has_next(): raise StopIteration("No more records") # Parse CSV line line = self._lines[self._index] parts = line.split(",") record = {"id": int(parts[0]), "name": parts[1], "source": "file"} self._index += 1 return record
# Step 5: Implement API Source with Iteratorclass APISource(DataSource): """API data source"""
def __init__(self, endpoint: str): self.endpoint = endpoint # Simulate API response self._records = [ {"id": 1, "name": "Alice", "source": "api"}, {"id": 2, "name": "Bob", "source": "api"}, {"id": 3, "name": "Charlie", "source": "api"} ]
def create_iterator(self) -> DataIterator: """Create API iterator""" return APIIterator(self)
def get_records(self): """Internal method to get records""" return self._records
class APIIterator(DataIterator): """Iterator for API source"""
def __init__(self, source: APISource): self._source = source self._records = source.get_records() self._index = 0
def has_next(self) -> bool: return self._index < len(self._records)
def next(self) -> Dict[str, Any]: if not self.has_next(): raise StopIteration("No more records") record = self._records[self._index] self._index += 1 return record
# Usage - Uniform interface for all sources!def process_data_source(source: DataSource): """Process any data source uniformly""" iterator = source.create_iterator()
print(f"Processing records from {type(source).__name__}:\n") while iterator.has_next(): record = iterator.next() print(f" Processing: {record}") print()
def main(): # Create different sources db_source = DatabaseSource("postgresql://localhost/db") file_source = FileSource("data.csv") api_source = APISource("https://api.example.com/data")
# Process all sources uniformly - same code! process_data_source(db_source) process_data_source(file_source) process_data_source(api_source)
print("✅ Iterator Pattern: Uniform interface for all data sources!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define Iterator interfaceinterface DataIterator { // Iterator interface for data sources boolean hasNext(); Map<String, Object> next();}
// Step 2: Define Iterable interfaceinterface DataSource { // Iterable interface for data sources DataIterator createIterator();}
// Step 3: Implement Database Source with Iteratorclass DatabaseSource implements DataSource { // Database data source private String connectionString; private List<Map<String, Object>> records;
public DatabaseSource(String connectionString) { this.connectionString = connectionString; // Simulate database records this.records = new ArrayList<>(); records.add(Map.of("id", 1, "name", "Alice", "source", "database")); records.add(Map.of("id", 2, "name", "Bob", "source", "database")); records.add(Map.of("id", 3, "name", "Charlie", "source", "database")); }
@Override public DataIterator createIterator() { // Create database iterator return new DatabaseIterator(this); }
List<Map<String, Object>> getRecords() { // Internal method to get records return records; }}
class DatabaseIterator implements DataIterator { // Iterator for database source private DatabaseSource source; private List<Map<String, Object>> records; private int index;
public DatabaseIterator(DatabaseSource source) { this.source = source; this.records = source.getRecords(); this.index = 0; }
@Override public boolean hasNext() { return index < records.size(); }
@Override public Map<String, Object> next() { if (!hasNext()) { throw new NoSuchElementException("No more records"); } return records.get(index++); }}
// Step 4: Implement File Source with Iteratorclass FileSource implements DataSource { // File data source private String filepath; private List<String> lines;
public FileSource(String filepath) { this.filepath = filepath; // Simulate file lines this.lines = Arrays.asList("id,name", "1,Alice", "2,Bob", "3,Charlie"); }
@Override public DataIterator createIterator() { // Create file iterator return new FileIterator(this); }
List<String> getLines() { // Internal method to get lines return lines; }}
class FileIterator implements DataIterator { // Iterator for file source private FileSource source; private List<String> lines; private int index;
public FileIterator(FileSource source) { this.source = source; this.lines = source.getLines().subList(1, source.getLines().size()); // Skip header this.index = 0; }
@Override public boolean hasNext() { return index < lines.size(); }
@Override public Map<String, Object> next() { if (!hasNext()) { throw new NoSuchElementException("No more records"); } // Parse CSV line String line = lines.get(index++); String[] parts = line.split(","); Map<String, Object> record = new HashMap<>(); record.put("id", Integer.parseInt(parts[0])); record.put("name", parts[1]); record.put("source", "file"); return record; }}
// Step 5: Implement API Source with Iteratorclass APISource implements DataSource { // API data source private String endpoint; private List<Map<String, Object>> records;
public APISource(String endpoint) { this.endpoint = endpoint; // Simulate API response this.records = new ArrayList<>(); records.add(Map.of("id", 1, "name", "Alice", "source", "api")); records.add(Map.of("id", 2, "name", "Bob", "source", "api")); records.add(Map.of("id", 3, "name", "Charlie", "source", "api")); }
@Override public DataIterator createIterator() { // Create API iterator return new APIIterator(this); }
List<Map<String, Object>> getRecords() { // Internal method to get records return records; }}
class APIIterator implements DataIterator { // Iterator for API source private APISource source; private List<Map<String, Object>> records; private int index;
public APIIterator(APISource source) { this.source = source; this.records = source.getRecords(); this.index = 0; }
@Override public boolean hasNext() { return index < records.size(); }
@Override public Map<String, Object> next() { if (!hasNext()) { throw new NoSuchElementException("No more records"); } return records.get(index++); }}
// Usage - Uniform interface for all sources!public class Main { public static void processDataSource(DataSource source) { // Process any data source uniformly DataIterator iterator = source.createIterator();
System.out.println("Processing records from " + source.getClass().getSimpleName() + ":\n"); while (iterator.hasNext()) { Map<String, Object> record = iterator.next(); System.out.println(" Processing: " + record); } System.out.println(); }
public static void main(String[] args) { // Create different sources DatabaseSource dbSource = new DatabaseSource("postgresql://localhost/db"); FileSource fileSource = new FileSource("data.csv"); APISource apiSource = new APISource("https://api.example.com/data");
// Process all sources uniformly - same code! processDataSource(dbSource); processDataSource(fileSource); processDataSource(apiSource);
System.out.println("✅ Iterator Pattern: Uniform interface for all data sources!"); }}// Step 1: Define Iterator interfaceinterface DataIterator { /** Iterator interface for data sources */ hasNext(): boolean; next(): Record<string, any>;}
// Step 2: Define Iterable interfaceinterface DataSource { /** Iterable interface for data sources */ createIterator(): DataIterator;}
// Step 3: Implement Database Source with Iteratorclass DatabaseSource implements DataSource { /** Database data source */ private connectionString: string; private records: Record<string, any>[];
constructor(connectionString: string) { this.connectionString = connectionString; // Simulate database records this.records = [ { id: 1, name: "Alice", source: "database" }, { id: 2, name: "Bob", source: "database" }, { id: 3, name: "Charlie", source: "database" } ]; }
createIterator(): DataIterator { // Create database iterator return new DatabaseIterator(this); }
getRecords(): Record<string, any>[] { return this.records; }}
class DatabaseIterator implements DataIterator { /** Iterator for database source */ private source: DatabaseSource; private records: Record<string, any>[]; private index: number = 0;
constructor(source: DatabaseSource) { this.source = source; this.records = source.getRecords(); }
hasNext(): boolean { return this.index < this.records.length; }
next(): Record<string, any> { if (!this.hasNext()) { throw new Error("No more records"); } return this.records[this.index++]; }}
// Step 4: Implement File Source with Iteratorclass FileSource implements DataSource { /** File data source */ private filepath: string; private lines: string[];
constructor(filepath: string) { this.filepath = filepath; // Simulate file reading this.lines = ["id,name", "1,Alice", "2,Bob", "3,Charlie"]; }
createIterator(): DataIterator { // Create file iterator return new FileIterator(this); }
getLines(): string[] { return this.lines.slice(1); // Skip header }}
class FileIterator implements DataIterator { /** Iterator for file source */ private source: FileSource; private lines: string[]; private index: number = 0;
constructor(source: FileSource) { this.source = source; this.lines = source.getLines(); }
hasNext(): boolean { return this.index < this.lines.length; }
next(): Record<string, any> { if (!this.hasNext()) { throw new Error("No more records"); } const line = this.lines[this.index++]; const [id, name] = line.split(","); return { id, name, source: "file" }; }}
// Step 5: Implement API Source with Iteratorclass APISource implements DataSource { /** API data source */ private endpoint: string; private records: Record<string, any>[];
constructor(endpoint: string) { this.endpoint = endpoint; // Simulate API response this.records = [ { id: 1, name: "Alice", source: "api" }, { id: 2, name: "Bob", source: "api" }, { id: 3, name: "Charlie", source: "api" } ]; }
createIterator(): DataIterator { // Create API iterator return new APIIterator(this); }
getRecords(): Record<string, any>[] { return this.records; }}
class APIIterator implements DataIterator { /** Iterator for API source */ private source: APISource; private records: Record<string, any>[]; private index: number = 0;
constructor(source: APISource) { this.source = source; this.records = source.getRecords(); }
hasNext(): boolean { return this.index < this.records.length; }
next(): Record<string, any> { if (!this.hasNext()) { throw new Error("No more records"); } return this.records[this.index++]; }}
// Usage - Uniform interface for all sources!function processDataSource(source: DataSource): void { /** Process any data source uniformly */ const iterator = source.createIterator();
console.log(`Processing records from ${source.constructor.name}:\n`); while (iterator.hasNext()) { const record = iterator.next(); console.log(` Processing: ${JSON.stringify(record)}`); } console.log();}
function main(): void { // Create different sources const dbSource = new DatabaseSource("postgresql://localhost/db"); const fileSource = new FileSource("data.csv"); const apiSource = new APISource("https://api.example.com/data");
// Process all sources uniformly - same code! processDataSource(dbSource); processDataSource(fileSource); processDataSource(apiSource);
console.log("✅ Iterator Pattern: Uniform interface for all data sources!");}
main();#include <iostream>#include <vector>#include <map>#include <string>#include <memory>#include <sstream>#include <stdexcept>
// Step 1: Define Iterator interfaceclass DataIterator {public: virtual ~DataIterator() = default; // Iterator interface for data sources virtual bool hasNext() = 0; virtual std::map<std::string, std::string> next() = 0;};
// Step 2: Define Iterable interfaceclass DataSource {public: virtual ~DataSource() = default; // Iterable interface for data sources virtual std::unique_ptr<DataIterator> createIterator() = 0;};
// Step 3: Implement Database Source with Iteratorclass DatabaseSource : public DataSource {private: std::string connectionString; std::vector<std::map<std::string, std::string>> records;
public: DatabaseSource(const std::string& connectionString) : connectionString(connectionString) { // Simulate database records records.push_back({{"id", "1"}, {"name", "Alice"}, {"source", "database"}}); records.push_back({{"id", "2"}, {"name", "Bob"}, {"source", "database"}}); records.push_back({{"id", "3"}, {"name", "Charlie"}, {"source", "database"}}); }
std::unique_ptr<DataIterator> createIterator() override;
const std::vector<std::map<std::string, std::string>>& getRecords() const { return records; }};
class DatabaseIterator : public DataIterator {private: const DatabaseSource* source; std::vector<std::map<std::string, std::string>> records; size_t index;
public: DatabaseIterator(const DatabaseSource* source) : source(source), index(0) { records = source->getRecords(); }
bool hasNext() override { return index < records.size(); }
std::map<std::string, std::string> next() override { if (!hasNext()) { throw std::runtime_error("No more records"); } return records[index++]; }};
std::unique_ptr<DataIterator> DatabaseSource::createIterator() { return std::make_unique<DatabaseIterator>(this);}
// Step 4: Implement File Source with Iteratorclass FileSource : public DataSource {private: std::string filepath; std::vector<std::string> lines;
public: FileSource(const std::string& filepath) : filepath(filepath) { // Simulate file reading lines = {"id,name", "1,Alice", "2,Bob", "3,Charlie"}; }
std::unique_ptr<DataIterator> createIterator() override;
std::vector<std::string> getLines() const { std::vector<std::string> result(lines.begin() + 1, lines.end()); return result; }};
class FileIterator : public DataIterator {private: const FileSource* source; std::vector<std::string> lines; size_t index;
public: FileIterator(const FileSource* source) : source(source), index(0) { lines = source->getLines(); }
bool hasNext() override { return index < lines.size(); }
std::map<std::string, std::string> next() override { if (!hasNext()) { throw std::runtime_error("No more records"); } std::string line = lines[index++]; size_t pos = line.find(','); std::string id = line.substr(0, pos); std::string name = line.substr(pos + 1); return {{"id", id}, {"name", name}, {"source", "file"}}; }};
std::unique_ptr<DataIterator> FileSource::createIterator() { return std::make_unique<FileIterator>(this);}
// Step 5: Implement API Source with Iteratorclass APISource : public DataSource {private: std::string endpoint; std::vector<std::map<std::string, std::string>> records;
public: APISource(const std::string& endpoint) : endpoint(endpoint) { // Simulate API response records.push_back({{"id", "1"}, {"name", "Alice"}, {"source", "api"}}); records.push_back({{"id", "2"}, {"name", "Bob"}, {"source", "api"}}); records.push_back({{"id", "3"}, {"name", "Charlie"}, {"source", "api"}}); }
std::unique_ptr<DataIterator> createIterator() override;
const std::vector<std::map<std::string, std::string>>& getRecords() const { return records; }};
class APIIterator : public DataIterator {private: const APISource* source; std::vector<std::map<std::string, std::string>> records; size_t index;
public: APIIterator(const APISource* source) : source(source), index(0) { records = source->getRecords(); }
bool hasNext() override { return index < records.size(); }
std::map<std::string, std::string> next() override { if (!hasNext()) { throw std::runtime_error("No more records"); } return records[index++]; }};
std::unique_ptr<DataIterator> APISource::createIterator() { return std::make_unique<APIIterator>(this);}
// Usage - Uniform interface for all sources!void processDataSource(DataSource& source) { // Process any data source uniformly auto iterator = source.createIterator();
std::cout << "Processing records:\n" << std::endl; while (iterator->hasNext()) { auto record = iterator->next(); std::cout << " Processing: {id: " << record["id"] << ", name: " << record["name"] << ", source: " << record["source"] << "}" << std::endl; } std::cout << std::endl;}
int main() { // Create different sources DatabaseSource dbSource("postgresql://localhost/db"); FileSource fileSource("data.csv"); APISource apiSource("https://api.example.com/data");
// Process all sources uniformly - same code! processDataSource(dbSource); processDataSource(fileSource); processDataSource(apiSource);
std::cout << "✅ Iterator Pattern: Uniform interface for all data sources!" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define Iterator interfacepublic interface IDataIterator{ /** Iterator interface for data sources */ bool HasNext(); Dictionary<string, object> Next();}
// Step 2: Define Iterable interfacepublic interface IDataSource{ /** Iterable interface for data sources */ IDataIterator CreateIterator();}
// Step 3: Implement Database Source with Iteratorpublic class DatabaseSource : IDataSource{ /** Database data source */ private string connectionString; private List<Dictionary<string, object>> records;
public DatabaseSource(string connectionString) { this.connectionString = connectionString; // Simulate database records this.records = new List<Dictionary<string, object>> { new Dictionary<string, object> { ["id"] = 1, ["name"] = "Alice", ["source"] = "database" }, new Dictionary<string, object> { ["id"] = 2, ["name"] = "Bob", ["source"] = "database" }, new Dictionary<string, object> { ["id"] = 3, ["name"] = "Charlie", ["source"] = "database" } }; }
public IDataIterator CreateIterator() { // Create database iterator return new DatabaseIterator(this); }
internal List<Dictionary<string, object>> GetRecords() { return records; }}
public class DatabaseIterator : IDataIterator{ /** Iterator for database source */ private DatabaseSource source; private List<Dictionary<string, object>> records; private int index = 0;
public DatabaseIterator(DatabaseSource source) { this.source = source; this.records = source.GetRecords(); }
public bool HasNext() { return index < records.Count; }
public Dictionary<string, object> Next() { if (!HasNext()) { throw new InvalidOperationException("No more records"); } return records[index++]; }}
// Step 4: Implement File Source with Iteratorpublic class FileSource : IDataSource{ /** File data source */ private string filepath; private List<string> lines;
public FileSource(string filepath) { this.filepath = filepath; // Simulate file reading this.lines = new List<string> { "id,name", "1,Alice", "2,Bob", "3,Charlie" }; }
public IDataIterator CreateIterator() { // Create file iterator return new FileIterator(this); }
internal List<string> GetLines() { return lines.GetRange(1, lines.Count - 1); // Skip header }}
public class FileIterator : IDataIterator{ /** Iterator for file source */ private FileSource source; private List<string> lines; private int index = 0;
public FileIterator(FileSource source) { this.source = source; this.lines = source.GetLines(); }
public bool HasNext() { return index < lines.Count; }
public Dictionary<string, object> Next() { if (!HasNext()) { throw new InvalidOperationException("No more records"); } string line = lines[index++]; string[] parts = line.Split(','); return new Dictionary<string, object> { ["id"] = parts[0], ["name"] = parts[1], ["source"] = "file" }; }}
// Step 5: Implement API Source with Iteratorpublic class APISource : IDataSource{ /** API data source */ private string endpoint; private List<Dictionary<string, object>> records;
public APISource(string endpoint) { this.endpoint = endpoint; // Simulate API response this.records = new List<Dictionary<string, object>> { new Dictionary<string, object> { ["id"] = 1, ["name"] = "Alice", ["source"] = "api" }, new Dictionary<string, object> { ["id"] = 2, ["name"] = "Bob", ["source"] = "api" }, new Dictionary<string, object> { ["id"] = 3, ["name"] = "Charlie", ["source"] = "api" } }; }
public IDataIterator CreateIterator() { // Create API iterator return new APIIterator(this); }
internal List<Dictionary<string, object>> GetRecords() { return records; }}
public class APIIterator : IDataIterator{ /** Iterator for API source */ private APISource source; private List<Dictionary<string, object>> records; private int index = 0;
public APIIterator(APISource source) { this.source = source; this.records = source.GetRecords(); }
public bool HasNext() { return index < records.Count; }
public Dictionary<string, object> Next() { if (!HasNext()) { throw new InvalidOperationException("No more records"); } return records[index++]; }}
// Usage - Uniform interface for all sources!class Program{ static void ProcessDataSource(IDataSource source) { // Process any data source uniformly IDataIterator iterator = source.CreateIterator();
Console.WriteLine($"Processing records from {source.GetType().Name}:\n"); while (iterator.HasNext()) { Dictionary<string, object> record = iterator.Next(); Console.WriteLine($" Processing: {record}"); } Console.WriteLine(); }
static void Main() { // Create different sources DatabaseSource dbSource = new DatabaseSource("postgresql://localhost/db"); FileSource fileSource = new FileSource("data.csv"); APISource apiSource = new APISource("https://api.example.com/data");
// Process all sources uniformly - same code! ProcessDataSource(dbSource); ProcessDataSource(fileSource); ProcessDataSource(apiSource);
Console.WriteLine("✅ Iterator Pattern: Uniform interface for all data sources!"); }}package main
import ( "fmt" "strings")
// Step 1: DataIterator interfacetype DataIterator interface { HasNext() bool Next() map[string]string}
// Step 2: DataSource interfacetype DataSource interface { CreateIterator() DataIterator}
// Step 3: DatabaseSourcetype DatabaseSource struct{ records []map[string]string }
func NewDatabaseSource() *DatabaseSource { return &DatabaseSource{records: []map[string]string{ {"id": "1", "name": "Alice", "source": "database"}, {"id": "2", "name": "Bob", "source": "database"}, {"id": "3", "name": "Charlie", "source": "database"}, }}}func (d *DatabaseSource) CreateIterator() DataIterator { return &SliceIterator{records: d.records}}
// Step 4: FileSourcetype FileSource struct{ lines []string }
func NewFileSource() *FileSource { return &FileSource{lines: []string{"id,name", "1,Alice", "2,Bob", "3,Charlie"}}}func (f *FileSource) CreateIterator() DataIterator { var records []map[string]string for _, line := range f.lines[1:] { parts := strings.SplitN(line, ",", 2) records = append(records, map[string]string{"id": parts[0], "name": parts[1], "source": "file"}) } return &SliceIterator{records: records}}
// Generic slice-based iteratortype SliceIterator struct { records []map[string]string index int}
func (it *SliceIterator) HasNext() bool { return it.index < len(it.records) }func (it *SliceIterator) Next() map[string]string { r := it.records[it.index]; it.index++; return r }
// Usage - Uniform interface for all sources!func processDataSource(source DataSource) { it := source.CreateIterator() for it.HasNext() { record := it.Next() fmt.Println(" Processing:", record) }}
func main() { processDataSource(NewDatabaseSource()) processDataSource(NewFileSource()) fmt.Println("✅ Iterator Pattern: Uniform interface for all data sources!")}// The Solution: Iterator Patternstruct Playlist { songs: Vec<String>,}
impl Playlist { fn iter(&self) -> std::slice::Iter<'_, String> { self.songs.iter() }}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; for song in playlist.iter() { println!("{}", song); }}Iterator Pattern Variants
Section titled “Iterator Pattern Variants”There are different ways to implement the Iterator Pattern:
1. External Iterator (Standard)
Section titled “1. External Iterator (Standard)”Client controls iteration:
# External Iterator - client controls iterationclass Iterator: def has_next(self): pass def next(self): pass
# Client controls iterationiterator = collection.create_iterator()while iterator.has_next(): item = iterator.next() process(item)// External Iterator - client controls iterationinterface Iterator<T> { boolean hasNext(); T next();}
// Client controls iterationIterator<T> iterator = collection.createIterator();while (iterator.hasNext()) { T item = iterator.next(); process(item);}// External Iterator - client controls iterationinterface Iterator<T> { hasNext(): boolean; next(): T;}
// Client controls iterationconst iterator = collection.createIterator();while (iterator.hasNext()) { const item = iterator.next(); process(item);}// External Iterator - client controls iterationtemplate<typename T>class Iterator {public: virtual ~Iterator() = default; virtual bool hasNext() = 0; virtual T next() = 0;};
// Client controls iterationauto iterator = collection.createIterator();while (iterator->hasNext()) { T item = iterator->next(); process(item);}// External Iterator - client controls iterationpublic interface IIterator<T>{ bool HasNext(); T Next();}
// Client controls iterationIIterator<T> iterator = collection.CreateIterator();while (iterator.HasNext()){ T item = iterator.Next(); Process(item);}// External Iterator - client controls iterationtype Iterator[T any] interface { HasNext() bool Next() T}
// Client controls iteration// it := collection.CreateIterator()// for it.HasNext() {// item := it.Next()// process(item)// }// 1. External Iterator Standardstruct Playlist { songs: Vec<String>,}
impl Playlist { fn iter(&self) -> std::slice::Iter<'_, String> { self.songs.iter() }}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; for song in playlist.iter() { println!("{}", song); }}Pros: Client has full control, flexible
Cons: More code for client
2. Internal Iterator
Section titled “2. Internal Iterator”Collection controls iteration, client provides callback:
# Internal Iterator - collection controls iterationclass Collection: def for_each(self, callback): for item in self._items: callback(item)
# Collection controls iterationcollection.for_each(lambda item: process(item))// Internal Iterator - collection controls iterationinterface Collection<T> { void forEach(Consumer<T> callback);}
// Collection controls iterationcollection.forEach(item -> process(item));// Internal Iterator - collection controls iterationinterface Collection<T> { forEach(callback: (item: T) => void): void;}
// Collection controls iterationcollection.forEach(item => process(item));#include <functional>
// Internal Iterator - collection controls iterationtemplate<typename T>class Collection {public: virtual ~Collection() = default; virtual void forEach(std::function<void(T)> callback) = 0;};
// Collection controls iterationcollection.forEach([](T item) { process(item); });using System;
// Internal Iterator - collection controls iterationpublic interface ICollection<T>{ void ForEach(Action<T> callback);}
// Collection controls iterationcollection.ForEach(item => Process(item));// Internal Iterator - collection controls iterationtype Collection[T any] struct{ items []T }
func (c *Collection[T]) ForEach(fn func(T)) { for _, item := range c.items { fn(item) }}
// Collection controls iteration// collection.ForEach(func(item T) { process(item) })// 2. Internal Iteratorstruct Playlist { songs: Vec<String>,}
impl Playlist { fn iter(&self) -> std::slice::Iter<'_, String> { self.songs.iter() }}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; for song in playlist.iter() { println!("{}", song); }}Pros: Simpler client code, less error-prone
Cons: Less control for client
When to Use Iterator Pattern?
Section titled “When to Use Iterator Pattern?”Use Iterator Pattern when:
✅ You want to traverse collections uniformly - Same interface for different types
✅ You want to hide collection structure - Clients don’t need implementation details
✅ You want multiple traversals - Support multiple simultaneous iterators
✅ You want to decouple traversal logic - Separate iteration from collection
✅ You want to support different iteration strategies - Forward, backward, filtered
When NOT to Use Iterator Pattern?
Section titled “When NOT to Use Iterator Pattern?”Don’t use Iterator Pattern when:
❌ Simple collections - If you only have one collection type, direct iteration is simpler
❌ Performance critical - Iterator adds indirection (usually negligible)
❌ Collections are too simple - For arrays or simple lists, direct access might be better
❌ Over-engineering - Don’t add complexity for simple cases
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Exposing Internal Structure
Section titled “Mistake 1: Exposing Internal Structure”# ❌ Bad: Iterator exposes internal structureclass Iterator: def __init__(self, collection): self.collection = collection # Bad: Exposes collection self.index = 0
def get_collection(self): # Bad: Exposes internal structure return self.collection
# ✅ Good: Iterator hides internal structureclass Iterator: def __init__(self, collection): self._collection = collection # Good: Private self._index = 0
def has_next(self): return self._index < self._collection._size() # Good: Uses interface// ❌ Bad: Iterator exposes internal structureclass Iterator { public Collection collection; // Bad: Exposes collection private int index;
public Collection getCollection() { // Bad: Exposes internal structure return collection; }}
// ✅ Good: Iterator hides internal structureclass Iterator { private Collection collection; // Good: Private private int index;
public boolean hasNext() { return index < collection.size(); // Good: Uses interface }}// ❌ Bad: Iterator exposes internal structureclass BadIterator { public collection: Collection; // Bad: Exposes collection private index: number;
constructor(collection: Collection) { this.collection = collection; this.index = 0; }
getCollection(): Collection { // Bad: Exposes internal structure return this.collection; }}
// ✅ Good: Iterator hides internal structureclass GoodIterator { private collection: Collection; // Good: Private private index: number;
constructor(collection: Collection) { this.collection = collection; this.index = 0; }
hasNext(): boolean { return this.index < this.collection.size(); // Good: Uses interface }}// ❌ Bad: Iterator exposes internal structureclass BadIterator {public: Collection* collection; // Bad: Exposes collectionprivate: int index;
public: Collection* getCollection() { // Bad: Exposes internal structure return collection; }};
// ✅ Good: Iterator hides internal structureclass GoodIterator {private: Collection* collection; // Good: Private int index;
public: bool hasNext() { return index < collection->size(); // Good: Uses interface }};// ❌ Bad: Iterator exposes internal structurepublic class BadIterator{ public Collection collection; // Bad: Exposes collection private int index;
public Collection GetCollection() // Bad: Exposes internal structure { return collection; }}
// ✅ Good: Iterator hides internal structurepublic class GoodIterator{ private Collection collection; // Good: Private private int index;
public bool HasNext() { return index < collection.Size(); // Good: Uses interface }}// ❌ Bad: Iterator exposes internal structuretype BadIterator struct { Collection *MyCollection // Bad: exported field exposes collection index int}
// ✅ Good: Iterator hides internal structuretype GoodIterator struct { collection *MyCollection // Good: unexported, private index int}
func (it *GoodIterator) HasNext() bool { return it.index < it.collection.Size() // Good: uses interface}// Mistake 1: Exposing Internal Structurestruct Playlist { songs: Vec<String>,}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; // Client depends on Vec internals. for i in 0..playlist.songs.len() { println!("{}", playlist.songs[i]); }}Mistake 2: Modifying Collection During Iteration
Section titled “Mistake 2: Modifying Collection During Iteration”# ❌ Bad: Modifying collection during iterationiterator = collection.create_iterator()while iterator.has_next(): item = iterator.next() collection.remove(item) # Bad: Modifies during iteration!
# ✅ Good: Collect items to remove, then removeitems_to_remove = []iterator = collection.create_iterator()while iterator.has_next(): item = iterator.next() if should_remove(item): items_to_remove.append(item)
for item in items_to_remove: collection.remove(item)// ❌ Bad: Modifying collection during iterationIterator<T> iterator = collection.createIterator();while (iterator.hasNext()) { T item = iterator.next(); collection.remove(item); // Bad: Modifies during iteration!}
// ✅ Good: Collect items to remove, then removeList<T> itemsToRemove = new ArrayList<>();Iterator<T> iterator = collection.createIterator();while (iterator.hasNext()) { T item = iterator.next(); if (shouldRemove(item)) { itemsToRemove.add(item); }}
for (T item : itemsToRemove) { collection.remove(item);}// ❌ Bad: Modifying collection during iterationconst iterator = collection.createIterator();while (iterator.hasNext()) { const item = iterator.next(); collection.remove(item); // Bad: Modifies during iteration!}
// ✅ Good: Collect items to remove, then removeconst itemsToRemove: T[] = [];const iterator2 = collection.createIterator();while (iterator2.hasNext()) { const item = iterator2.next(); if (shouldRemove(item)) { itemsToRemove.push(item); }}
for (const item of itemsToRemove) { collection.remove(item);}#include <vector>
// ❌ Bad: Modifying collection during iterationauto iterator = collection.createIterator();while (iterator->hasNext()) { T item = iterator->next(); collection.remove(item); // Bad: Modifies during iteration!}
// ✅ Good: Collect items to remove, then removestd::vector<T> itemsToRemove;auto iterator2 = collection.createIterator();while (iterator2->hasNext()) { T item = iterator2->next(); if (shouldRemove(item)) { itemsToRemove.push_back(item); }}
for (const T& item : itemsToRemove) { collection.remove(item);}using System.Collections.Generic;
// ❌ Bad: Modifying collection during iterationIIterator<T> iterator = collection.CreateIterator();while (iterator.HasNext()){ T item = iterator.Next(); collection.Remove(item); // Bad: Modifies during iteration!}
// ✅ Good: Collect items to remove, then removeList<T> itemsToRemove = new List<T>();IIterator<T> iterator2 = collection.CreateIterator();while (iterator2.HasNext()){ T item = iterator2.Next(); if (ShouldRemove(item)) { itemsToRemove.Add(item); }}
foreach (T item in itemsToRemove){ collection.Remove(item);}// ❌ Bad: Modifying collection during iteration// for it.HasNext() {// item := it.Next()// collection.Remove(item) // Bad: modifies during iteration!// }
// ✅ Good in Go: collect items then remove, or use for range on a copyvar toRemove []stringfor _, item := range collection.items { if shouldRemove(item) { toRemove = append(toRemove, item) }}for _, item := range toRemove { collection.Remove(item)}// Mistake 2: Modifying Collection During Iterationstruct Playlist { songs: Vec<String>,}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; // Client depends on Vec internals. for i in 0..playlist.songs.len() { println!("{}", playlist.songs[i]); }}Mistake 3: Not Handling Concurrent Modifications
Section titled “Mistake 3: Not Handling Concurrent Modifications”# ❌ Bad: No protection against concurrent modificationclass Iterator: def __init__(self, collection): self._collection = collection self._index = 0
def next(self): return self._collection.get(self._index) # Bad: No check!
# ✅ Good: Check for concurrent modificationclass Iterator: def __init__(self, collection): self._collection = collection self._index = 0 self._expected_size = collection.size() # Good: Track size
def next(self): if self._collection.size() != self._expected_size: raise ConcurrentModificationError() # Good: Detect changes return self._collection.get(self._index)// ❌ Bad: No protection against concurrent modificationclass Iterator { private Collection collection; private int index;
public T next() { return collection.get(index); // Bad: No check! }}
// ✅ Good: Check for concurrent modificationclass Iterator { private Collection collection; private int index; private int expectedModCount; // Good: Track modifications
public T next() { if (collection.getModCount() != expectedModCount) { throw new ConcurrentModificationException(); // Good: Detect changes } return collection.get(index); }}// ❌ Bad: No protection against concurrent modificationclass BadIterator { private collection: Collection; private index: number = 0;
constructor(collection: Collection) { this.collection = collection; }
next(): T { return this.collection.get(this.index); // Bad: No check! }}
// ✅ Good: Check for concurrent modificationclass GoodIterator { private collection: Collection; private index: number = 0; private expectedSize: number; // Good: Track size
constructor(collection: Collection) { this.collection = collection; this.expectedSize = collection.size(); }
next(): T { if (this.collection.size() !== this.expectedSize) { throw new Error("ConcurrentModificationError"); // Good: Detect changes } return this.collection.get(this.index); }}#include <stdexcept>
// ❌ Bad: No protection against concurrent modificationclass BadIterator {private: Collection* collection; int index;
public: T next() { return collection->get(index); // Bad: No check! }};
// ✅ Good: Check for concurrent modificationclass GoodIterator {private: Collection* collection; int index; int expectedModCount; // Good: Track modifications
public: GoodIterator(Collection* collection) : collection(collection), index(0), expectedModCount(collection->getModCount()) {}
T next() { if (collection->getModCount() != expectedModCount) { throw std::runtime_error("ConcurrentModificationException"); // Good: Detect changes } return collection->get(index); }};using System;
// ❌ Bad: No protection against concurrent modificationpublic class BadIterator{ private Collection collection; private int index = 0;
public BadIterator(Collection collection) { this.collection = collection; }
public T Next() { return collection.Get(index); // Bad: No check! }}
// ✅ Good: Check for concurrent modificationpublic class GoodIterator{ private Collection collection; private int index = 0; private int expectedSize; // Good: Track size
public GoodIterator(Collection collection) { this.collection = collection; this.expectedSize = collection.Size(); }
public T Next() { if (collection.Size() != expectedSize) { throw new InvalidOperationException("ConcurrentModificationException"); // Good: Detect changes } return collection.Get(index); }}import "errors"
// ❌ Bad: No protection against concurrent modificationtype BadIterator struct { items []string index int}func (it *BadIterator) Next() string { return it.items[it.index] } // No check!
// ✅ Good: Track snapshot size to detect modificationtype SafeIterator struct { items []string index int expectedLen int}
func NewSafeIterator(items []string) *SafeIterator { return &SafeIterator{items: items, expectedLen: len(items)}}
func (it *SafeIterator) Next() (string, error) { if len(it.items) != it.expectedLen { return "", errors.New("concurrent modification detected") } item := it.items[it.index] it.index++ return item, nil}// Mistake 3: Not Handling Concurrent Modificationsstruct Playlist { songs: Vec<String>,}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; // Client depends on Vec internals. for i in 0..playlist.songs.len() { println!("{}", playlist.songs[i]); }}Benefits of Iterator Pattern
Section titled “Benefits of Iterator Pattern”- Uniform Interface - Same traversal interface for all collections
- Hidden Structure - Clients don’t know internal implementation
- Decoupled - Traversal logic separated from collection
- Multiple Traversals - Support multiple simultaneous iterators
- Easy to Extend - Add new collection types without changing clients
- Distributed Systems Friendly - Works with remote data sources
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Iterator Pattern?
Section titled “What is Iterator Pattern?”Iterator Pattern is a behavioral design pattern that provides a way to access elements of an aggregate object sequentially without exposing its underlying representation.
Why Use It?
Section titled “Why Use It?”- ✅ Uniform traversal - Same interface for all collections
- ✅ Hide structure - Clients don’t know implementation
- ✅ Decouple - Traversal logic separated from collection
- ✅ Multiple traversals - Support multiple simultaneous iterators
- ✅ Easy to extend - Add new collections without changing clients
How It Works?
Section titled “How It Works?”- Define Iterator interface - Uniform traversal methods
- Define Iterable interface - Collections that can be iterated
- Implement Concrete Iterator - Traverses specific collection
- Implement Concrete Aggregate - Collection that creates iterator
- Client uses iterator - Uniform interface for all collections
Key Components
Section titled “Key Components”Client → Iterable → Iterator → Aggregate- Iterator - Interface for traversal
- Concrete Iterator - Implements traversal for specific collection
- Iterable - Interface for collections that can be iterated
- Concrete Aggregate - Collection that creates iterator
- Client - Uses iterator uniformly
Simple Example
Section titled “Simple Example”class Iterator: def has_next(self): pass def next(self): pass
class Collection: def create_iterator(self): return Iterator()
iterator = collection.create_iterator()while iterator.has_next(): item = iterator.next()interface Iterator<T> { boolean hasNext(); T next();}
interface Collection<T> { Iterator<T> createIterator();}
Iterator<T> it = collection.createIterator();while (it.hasNext()) { T item = it.next();}interface Iterator<T> { hasNext(): boolean; next(): T;}
interface Collection<T> { createIterator(): Iterator<T>;}
const it = collection.createIterator();while (it.hasNext()) { const item = it.next();}template<typename T>class Iterator {public: virtual bool hasNext() = 0; virtual T next() = 0;};
template<typename T>class Collection {public: virtual Iterator<T>* createIterator() = 0;};
auto* it = collection.createIterator();while (it->hasNext()) { T item = it->next();}interface IIterator<T> { bool HasNext { get; } T Next();}
interface ICollection<T> { IIterator<T> CreateIterator();}
var it = collection.CreateIterator();while (it.HasNext) { var item = it.Next();}type Iterator[T any] interface { HasNext() bool Next() T}
type Collection[T any] interface { CreateIterator() Iterator[T]}
// it := collection.CreateIterator()// for it.HasNext() { item := it.Next() }// Simple Examplestruct Playlist { songs: Vec<String>,}
impl Playlist { fn iter(&self) -> std::slice::Iter<'_, String> { self.songs.iter() }}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; for song in playlist.iter() { println!("{}", song); }}When to Use?
Section titled “When to Use?”✅ Traverse collections uniformly
✅ Hide collection structure
✅ Support multiple traversals
✅ Decouple traversal logic
✅ Support different iteration strategies
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple collections
❌ Performance critical
❌ Collections are too simple
❌ Over-engineering
Key Takeaways
Section titled “Key Takeaways”- Iterator Pattern = Uniform traversal interface
- Iterator = Traversal interface
- Iterable = Collection interface
- Benefit = Uniform interface, hidden structure
- Use Case = Multiple collection types, distributed data sources
Common Pattern Structure
Section titled “Common Pattern Structure”class Iterator: def has_next(self): pass def next(self): pass
class Collection: def create_iterator(self): return Iterator()interface Iterator<T> { boolean hasNext(); T next(); }interface Collection<T> { Iterator<T> createIterator(); }interface Iterator<T> { hasNext(): boolean; next(): T; }interface Collection<T> { createIterator(): Iterator<T>; }template<typename T>class Iterator { public: virtual bool hasNext()=0; virtual T next()=0; };template<typename T>class Collection { public: virtual Iterator<T>* createIterator()=0; };interface IIterator<T> { bool HasNext { get; } T Next(); }interface ICollection<T> { IIterator<T> CreateIterator(); }type Iterator[T any] interface { HasNext() bool; Next() T }type Collection[T any] interface { CreateIterator() Iterator[T] }// Common Pattern Structurestruct Playlist { songs: Vec<String>,}
impl Playlist { fn iter(&self) -> std::slice::Iter<'_, String> { self.songs.iter() }}
fn main() { let playlist = Playlist { songs: vec!["Intro".into(), "Deep Dive".into()], }; for song in playlist.iter() { println!("{}", song); }}Remember
Section titled “Remember”- Iterator Pattern provides uniform traversal interface
- It hides collection structure
- It decouples traversal from collection
- It’s about uniformity, not just iteration!
- Essential for distributed systems with multiple data sources!
Interview Focus: Iterator Pattern
Section titled “Interview Focus: Iterator Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Iterator Pattern is a behavioral design pattern that provides a way to access elements of an aggregate object sequentially without exposing its underlying representation. It decouples traversal logic from the collection structure, allowing uniform traversal of different collection types.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of decoupling and encapsulation
- Indicates you can explain concepts clearly
2. When to Use Iterator Pattern
Section titled “2. When to Use Iterator Pattern”Must mention:
- ✅ Multiple collection types - Need uniform traversal interface
- ✅ Hide implementation - Clients shouldn’t know internal structure
- ✅ Multiple traversals - Support simultaneous iterators
- ✅ Distributed systems - Uniform interface for remote data sources
- ✅ Decouple traversal - Separate iteration logic from collection
Example scenario to give:
“I’d use Iterator Pattern when building a data processing pipeline that needs to process records from multiple sources - database, file system, and API. Each source has different internal structure, but Iterator provides uniform traversal interface. Client code doesn’t need to know if data comes from database or file - it just iterates uniformly.”
3. Iterator vs Direct Access
Section titled “3. Iterator vs Direct Access”Must discuss:
- Iterator: Uniform interface, hides structure, decoupled
- Direct Access: Simple, but exposes structure, tight coupling
- Key difference: Iterator abstracts traversal, direct access exposes implementation
Example to give:
“Iterator Pattern abstracts traversal logic, allowing clients to traverse collections without knowing their internal structure. Direct access requires clients to know if collection is array, linked list, or tree, leading to tight coupling. Iterator provides uniform interface regardless of implementation.”
4. Distributed Systems Relevance
Section titled “4. Distributed Systems Relevance”Must discuss:
- Multiple data sources - Database, file system, API, message queues
- Uniform interface - Same traversal code for all sources
- Lazy loading - Can load data on-demand during iteration
- Network efficiency - Can implement pagination in iterator
Example to give:
“In distributed systems, Iterator Pattern is essential for processing data from multiple sources uniformly. For example, a data pipeline might need to process records from database, S3 files, and Kafka streams. Iterator provides uniform interface, and can implement lazy loading and pagination to handle large datasets efficiently.”
5. Benefits and Trade-offs
Section titled “5. Benefits and Trade-offs”Benefits to mention:
- Uniform interface - Same traversal code for all collections
- Hidden structure - Clients don’t know implementation
- Decoupled - Traversal logic separated from collection
- Multiple traversals - Support simultaneous iterators
- Easy to extend - Add new collections without changing clients
Trade-offs to acknowledge:
- Complexity - Adds abstraction layer
- Performance - Small overhead (usually negligible)
- Over-engineering risk - Can be overkill for simple collections
6. Common Interview Questions
Section titled “6. Common Interview Questions”Q: “What’s the difference between Iterator Pattern and for-each loop?”
A:
“Iterator Pattern provides an abstraction layer that hides collection implementation and allows uniform traversal of different collection types. For-each loops work with specific collection types and expose their structure. Iterator Pattern is useful when you have multiple collection types or need to hide implementation, while for-each is simpler for single collection types.”
Q: “How would you implement Iterator for a distributed data source?”
A:
“For distributed data sources, I’d implement Iterator with lazy loading and pagination. The iterator would fetch data in batches from remote source, caching current batch. When has_next() is called, it checks if current batch has more items or fetches next batch. This allows processing large datasets without loading everything into memory.”
Q: “How does Iterator Pattern relate to SOLID principles?”
A:
“Iterator Pattern supports Single Responsibility Principle by separating traversal logic into iterator. It supports Open/Closed Principle - you can add new collection types without modifying client code. It supports Dependency Inversion Principle by having clients depend on Iterator interface rather than concrete collections. It also supports Interface Segregation Principle by providing focused iterator interface.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Iterator Pattern clearly in one sentence
- Explain when to use it (with examples showing uniform traversal)
- Describe Iterator vs Direct Access
- Implement Iterator Pattern from scratch
- Compare with other patterns (Visitor, Strategy)
- List benefits and trade-offs
- Identify common mistakes (exposing structure, concurrent modification)
- Give 2-3 real-world examples (especially distributed systems)
- Connect to SOLID principles
- Discuss when NOT to use it
- Explain distributed systems relevance
Remember: Iterator Pattern is about uniform traversal - providing a consistent way to access elements without exposing collection structure, essential for distributed systems! 🔄