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:
sequenceDiagram
participant Client
participant Collection as BookCollection
participant Iterator as BookIterator
Client->>Collection: create_iterator()
activate Collection
Collection->>Iterator: new BookIterator(collection)
activate Iterator
Iterator-->>Collection: Iterator created
deactivate Iterator
Collection-->>Client: Iterator
deactivate Collection
Client->>Iterator: has_next()
activate Iterator
Iterator->>Iterator: Check index < size
Iterator-->>Client: true
deactivate Iterator
Client->>Iterator: next()
activate Iterator
Iterator->>Iterator: Get element at index
Iterator->>Iterator: Increment index
Iterator-->>Client: Book("Design Patterns")
deactivate Iterator
Note over Client,Iterator: Iterator provides uniform\ninterface for traversal!
Client->>Iterator: has_next()
Iterator-->>Client: true
Client->>Iterator: next()
Iterator-->>Client: Book("Clean Code")
The Problem
Section titled “The Problem”You’re building a library system with different collection types (array, linked list). Without Iterator Pattern:
1# ❌ Without Iterator Pattern - Exposed internal structure!2
3class Book:4 """Book model"""5 def __init__(self, title: str):6 self.title = title7
8 def __str__(self):9 return self.title10
11class BookArray:12 """Book collection using array"""13 def __init__(self):14 self.books = [] # Array implementation15
16 def add(self, book: Book):17 self.books.append(book)18
19 def get(self, index: int) -> Book:20 return self.books[index]21
22 def size(self) -> int:23 return len(self.books)24
25class BookLinkedList:26 """Book collection using linked list"""27 class Node:28 def __init__(self, book: Book):29 self.book = book30 self.next = None31
32 def __init__(self):33 self.head = None # Linked list implementation34
35 def add(self, book: Book):36 node = self.Node(book)37 node.next = self.head38 self.head = node39
40 def get_first(self) -> Book:41 return self.head.book if self.head else None42
43# Problem: Client needs to know internal structure!44def traverse_array(collection: BookArray):45 # Client knows it's an array - uses index46 for i in range(collection.size()):47 book = collection.get(i)48 print(f"Reading: {book}")49
50def traverse_linked_list(collection: BookLinkedList):51 # Client knows it's a linked list - uses node traversal52 current = collection.head53 while current:54 print(f"Reading: {current.book}")55 current = current.next56
57# Problems:58# - Client needs to know collection type59# - Different traversal code for each type60# - Tight coupling to implementation61# - Hard to add new collection types1// ❌ Without Iterator Pattern - Exposed internal structure!2
3class Book {4 // Book model5 private String title;6
7 public Book(String title) {8 this.title = title;9 }10
11 @Override12 public String toString() {13 return title;14 }15}16
17class BookArray {18 // Book collection using array19 private List<Book> books = new ArrayList<>(); // Array implementation20
21 public void add(Book book) {22 books.add(book);23 }24
25 public Book get(int index) {26 return books.get(index);27 }28
29 public int size() {30 return books.size();31 }32}33
34class BookLinkedList {35 // Book collection using linked list36 class Node {37 Book book;38 Node next;39
40 Node(Book book) {41 this.book = book;42 }43 }44
45 private Node head; // Linked list implementation46
47 public void add(Book book) {48 Node node = new Node(book);49 node.next = head;50 head = node;51 }52
53 public Book getFirst() {54 return head != null ? head.book : null;55 }56}57
58// Problem: Client needs to know internal structure!59public static void traverseArray(BookArray collection) {60 // Client knows it's an array - uses index61 for (int i = 0; i < collection.size(); i++) {62 Book book = collection.get(i);63 System.out.println("Reading: " + book);64 }65}66
67public static void traverseLinkedList(BookLinkedList collection) {68 // Client knows it's a linked list - uses node traversal69 BookLinkedList.Node current = collection.head;70 while (current != null) {71 System.out.println("Reading: " + current.book);72 current = current.next;73 }74}75
76// Problems:77// - Client needs to know collection type78// - Different traversal code for each type79// - Tight coupling to implementation80// - Hard to add new collection typesProblems:
- 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”classDiagram
class Iterable {
<<interface>>
+create_iterator() Iterator
}
class Iterator {
<<interface>>
+has_next() bool
+next() Object
+current() Object
}
class BookCollection {
-books: List
+create_iterator() Iterator
+add(book) void
}
class BookIterator {
-collection: BookCollection
-index: int
+has_next() bool
+next() Book
+current() Book
}
Iterable <|.. BookCollection : implements
Iterator <|.. BookIterator : implements
BookCollection --> Iterator : creates
BookIterator --> BookCollection : traverses
note for Iterator "Uniform traversal interface"
note for BookCollection "Hides internal structure"
1from abc import ABC, abstractmethod2from typing import List, Optional3
4# Step 1: Define Iterator interface5class Iterator(ABC):6 """Iterator interface - uniform traversal interface"""7
8 @abstractmethod9 def has_next(self) -> bool:10 """Check if there are more elements"""11 pass12
13 @abstractmethod14 def next(self):15 """Get next element and advance"""16 pass17
18 @abstractmethod19 def current(self):20 """Get current element without advancing"""21 pass22
23# Step 2: Define Iterable interface24class Iterable(ABC):25 """Iterable interface - collections that can be iterated"""26
27 @abstractmethod28 def create_iterator(self) -> Iterator:29 """Create an iterator for this collection"""30 pass31
32# Step 3: Implement Book model33class Book:34 """Book model"""35 def __init__(self, title: str):36 self.title = title37
38 def __str__(self):39 return self.title40
41# Step 4: Implement Concrete Aggregate42class BookCollection(Iterable):43 """Concrete aggregate - book collection"""44
45 def __init__(self):46 self._books: List[Book] = [] # Internal structure hidden47
48 def add(self, book: Book) -> None:49 """Add a book to collection"""50 self._books.append(book)51
52 def get(self, index: int) -> Book:53 """Get book by index (internal method)"""54 return self._books[index]55
56 def size(self) -> int:57 """Get collection size (internal method)"""58 return len(self._books)59
60 def create_iterator(self) -> Iterator:61 """Create iterator for this collection"""62 return BookIterator(self)63
64# Step 5: Implement Concrete Iterator65class BookIterator(Iterator):66 """Concrete iterator - traverses book collection"""67
68 def __init__(self, collection: BookCollection):69 self._collection = collection70 self._index = 0 # Current position71
72 def has_next(self) -> bool:73 """Check if there are more books"""74 return self._index < self._collection.size()75
76 def next(self) -> Book:77 """Get next book and advance"""78 if not self.has_next():79 raise StopIteration("No more books")80
81 book = self._collection.get(self._index)82 self._index += 183 return book84
85 def current(self) -> Book:86 """Get current book without advancing"""87 if self._index >= self._collection.size():88 raise IndexError("No current book")89 return self._collection.get(self._index)90
91# Usage - Uniform interface!92def main():93 # Create collection94 collection = BookCollection()95 collection.add(Book("Design Patterns"))96 collection.add(Book("Clean Code"))97 collection.add(Book("Refactoring"))98
99 # Client uses uniform iterator interface - doesn't know internal structure!100 iterator = collection.create_iterator()101
102 print("Traversing books:\n")103 while iterator.has_next():104 book = iterator.next()105 print(f"Reading: {book}")106
107 print("\n✅ Iterator Pattern: Uniform interface for all collections!")108
109if __name__ == "__main__":110 main()1import java.util.*;2
3// Step 1: Define Iterator interface4interface Iterator<T> {5 // Iterator interface - uniform traversal interface6 boolean hasNext();7 T next();8 T current();9}10
11// Step 2: Define Iterable interface12interface Iterable<T> {13 // Iterable interface - collections that can be iterated14 Iterator<T> createIterator();15}16
17// Step 3: Implement Book model18class Book {19 // Book model20 private String title;21
22 public Book(String title) {23 this.title = title;24 }25
26 @Override27 public String toString() {28 return title;29 }30}31
32// Step 4: Implement Concrete Aggregate33class BookCollection implements Iterable<Book> {34 // Concrete aggregate - book collection35 private List<Book> books; // Internal structure hidden36
37 public BookCollection() {38 this.books = new ArrayList<>();39 }40
41 public void add(Book book) {42 // Add a book to collection43 books.add(book);44 }45
46 Book get(int index) {47 // Get book by index (internal method)48 return books.get(index);49 }50
51 int size() {52 // Get collection size (internal method)53 return books.size();54 }55
56 @Override57 public Iterator<Book> createIterator() {58 // Create iterator for this collection59 return new BookIterator(this);60 }61}62
63// Step 5: Implement Concrete Iterator64class BookIterator implements Iterator<Book> {65 // Concrete iterator - traverses book collection66 private BookCollection collection;67 private int index; // Current position68
69 public BookIterator(BookCollection collection) {70 this.collection = collection;71 this.index = 0;72 }73
74 @Override75 public boolean hasNext() {76 // Check if there are more books77 return index < collection.size();78 }79
80 @Override81 public Book next() {82 // Get next book and advance83 if (!hasNext()) {84 throw new NoSuchElementException("No more books");85 }86
87 Book book = collection.get(index);88 index++;89 return book;90 }91
92 @Override93 public Book current() {94 // Get current book without advancing95 if (index >= collection.size()) {96 throw new IndexOutOfBoundsException("No current book");97 }98 return collection.get(index);99 }100}101
102// Usage - Uniform interface!103public class Main {104 public static void main(String[] args) {105 // Create collection106 BookCollection collection = new BookCollection();107 collection.add(new Book("Design Patterns"));108 collection.add(new Book("Clean Code"));109 collection.add(new Book("Refactoring"));110
111 // Client uses uniform iterator interface - doesn't know internal structure!112 Iterator<Book> iterator = collection.createIterator();113
114 System.out.println("Traversing books:\n");115 while (iterator.hasNext()) {116 Book book = iterator.next();117 System.out.println("Reading: " + book);118 }119
120 System.out.println("\n✅ Iterator Pattern: Uniform interface for all collections!");121 }122}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:
1# ❌ Without Iterator Pattern - Different access patterns!2
3class DatabaseSource:4 """Database data source"""5 def __init__(self, connection_string: str):6 self.connection_string = connection_string7 # Simulate database connection8 self.records = [9 {"id": 1, "name": "Alice"},10 {"id": 2, "name": "Bob"},11 {"id": 3, "name": "Charlie"}12 ]13 self.current_index = 014
15 def fetch_all(self):16 """Fetch all records"""17 return self.records18
19 def fetch_next(self):20 """Fetch next record"""21 if self.current_index < len(self.records):22 record = self.records[self.current_index]23 self.current_index += 124 return record25 return None26
27class FileSource:28 """File data source"""29 def __init__(self, filepath: str):30 self.filepath = filepath31 # Simulate file reading32 self.lines = [33 "id,name",34 "1,Alice",35 "2,Bob",36 "3,Charlie"37 ]38 self.current_line = 039
40 def read_lines(self):41 """Read all lines"""42 return self.lines[1:] # Skip header43
44 def read_next_line(self):45 """Read next line"""46 if self.current_line < len(self.lines) - 1:47 line = self.lines[self.current_line + 1]48 self.current_line += 149 return line50 return None51
52class APISource:53 """API data source"""54 def __init__(self, endpoint: str):55 self.endpoint = endpoint56 # Simulate API response57 self.data = {58 "records": [59 {"id": 1, "name": "Alice"},60 {"id": 2, "name": "Bob"},61 {"id": 3, "name": "Charlie"}62 ]63 }64
65 def get_all(self):66 """Get all records"""67 return self.data["records"]68
69 def get_next_page(self):70 """Get next page (pagination)"""71 return self.data["records"]72
73# Problem: Client needs different code for each source!74def process_database(source: DatabaseSource):75 records = source.fetch_all() # Different method!76 for record in records:77 print(f"Processing: {record}")78
79def process_file(source: FileSource):80 lines = source.read_lines() # Different method!81 for line in lines:82 print(f"Processing: {line}")83
84def process_api(source: APISource):85 records = source.get_all() # Different method!86 for record in records:87 print(f"Processing: {record}")88
89# Problems:90# - Different access patterns for each source91# - Client needs to know source type92# - Hard to add new sources93# - Code duplication1// ❌ Without Iterator Pattern - Different access patterns!2
3class DatabaseSource {4 // Database data source5 private String connectionString;6 private List<Map<String, Object>> records;7 private int currentIndex;8
9 public DatabaseSource(String connectionString) {10 this.connectionString = connectionString;11 // Simulate database connection12 this.records = new ArrayList<>();13 records.add(Map.of("id", 1, "name", "Alice"));14 records.add(Map.of("id", 2, "name", "Bob"));15 records.add(Map.of("id", 3, "name", "Charlie"));16 this.currentIndex = 0;17 }18
19 public List<Map<String, Object>> fetchAll() {20 // Fetch all records21 return records;22 }23
24 public Map<String, Object> fetchNext() {25 // Fetch next record26 if (currentIndex < records.size()) {27 return records.get(currentIndex++);28 }29 return null;30 }31}32
33class FileSource {34 // File data source35 private String filepath;36 private List<String> lines;37 private int currentLine;38
39 public FileSource(String filepath) {40 this.filepath = filepath;41 // Simulate file reading42 this.lines = Arrays.asList("id,name", "1,Alice", "2,Bob", "3,Charlie");43 this.currentLine = 0;44 }45
46 public List<String> readLines() {47 // Read all lines48 return lines.subList(1, lines.size()); // Skip header49 }50
51 public String readNextLine() {52 // Read next line53 if (currentLine < lines.size() - 1) {54 return lines.get(++currentLine);55 }56 return null;57 }58}59
60class APISource {61 // API data source62 private String endpoint;63 private Map<String, List<Map<String, Object>>> data;64
65 public APISource(String endpoint) {66 this.endpoint = endpoint;67 // Simulate API response68 this.data = Map.of("records", Arrays.asList(69 Map.of("id", 1, "name", "Alice"),70 Map.of("id", 2, "name", "Bob"),71 Map.of("id", 3, "name", "Charlie")72 ));73 }74
75 public List<Map<String, Object>> getAll() {76 // Get all records77 return data.get("records");78 }79}80
81// Problem: Client needs different code for each source!82public static void processDatabase(DatabaseSource source) {83 List<Map<String, Object>> records = source.fetchAll(); // Different method!84 for (Map<String, Object> record : records) {85 System.out.println("Processing: " + record);86 }87}88
89public static void processFile(FileSource source) {90 List<String> lines = source.readLines(); // Different method!91 for (String line : lines) {92 System.out.println("Processing: " + line);93 }94}95
96public static void processAPI(APISource source) {97 List<Map<String, Object>> records = source.getAll(); // Different method!98 for (Map<String, Object> record : records) {99 System.out.println("Processing: " + record);100 }101}102
103// Problems:104// - Different access patterns for each source105// - Client needs to know source type106// - Hard to add new sources107// - Code duplicationProblems:
- 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”1from abc import ABC, abstractmethod2from typing import Dict, Any, Optional3
4# Step 1: Define Iterator interface5class DataIterator(ABC):6 """Iterator interface for data sources"""7
8 @abstractmethod9 def has_next(self) -> bool:10 """Check if there are more records"""11 pass12
13 @abstractmethod14 def next(self) -> Dict[str, Any]:15 """Get next record"""16 pass17
18# Step 2: Define Iterable interface19class DataSource(ABC):20 """Iterable interface for data sources"""21
22 @abstractmethod23 def create_iterator(self) -> DataIterator:24 """Create iterator for this data source"""25 pass26
27# Step 3: Implement Database Source with Iterator28class DatabaseSource(DataSource):29 """Database data source"""30
31 def __init__(self, connection_string: str):32 self.connection_string = connection_string33 # Simulate database records34 self._records = [35 {"id": 1, "name": "Alice", "source": "database"},36 {"id": 2, "name": "Bob", "source": "database"},37 {"id": 3, "name": "Charlie", "source": "database"}38 ]39
40 def create_iterator(self) -> DataIterator:41 """Create database iterator"""42 return DatabaseIterator(self)43
44 def get_records(self):45 """Internal method to get records"""46 return self._records47
48class DatabaseIterator(DataIterator):49 """Iterator for database source"""50
51 def __init__(self, source: DatabaseSource):52 self._source = source53 self._records = source.get_records()54 self._index = 055
56 def has_next(self) -> bool:57 return self._index < len(self._records)58
59 def next(self) -> Dict[str, Any]:60 if not self.has_next():61 raise StopIteration("No more records")62 record = self._records[self._index]63 self._index += 164 return record65
66# Step 4: Implement File Source with Iterator67class FileSource(DataSource):68 """File data source"""69
70 def __init__(self, filepath: str):71 self.filepath = filepath72 # Simulate file lines73 self._lines = [74 "id,name",75 "1,Alice",76 "2,Bob",77 "3,Charlie"78 ]79
80 def create_iterator(self) -> DataIterator:81 """Create file iterator"""82 return FileIterator(self)83
84 def get_lines(self):85 """Internal method to get lines"""86 return self._lines87
88class FileIterator(DataIterator):89 """Iterator for file source"""90
91 def __init__(self, source: FileSource):92 self._source = source93 self._lines = source.get_lines()[1:] # Skip header94 self._index = 095
96 def has_next(self) -> bool:97 return self._index < len(self._lines)98
99 def next(self) -> Dict[str, Any]:100 if not self.has_next():101 raise StopIteration("No more records")102 # Parse CSV line103 line = self._lines[self._index]104 parts = line.split(",")105 record = {"id": int(parts[0]), "name": parts[1], "source": "file"}106 self._index += 1107 return record108
109# Step 5: Implement API Source with Iterator110class APISource(DataSource):111 """API data source"""112
113 def __init__(self, endpoint: str):114 self.endpoint = endpoint115 # Simulate API response116 self._records = [117 {"id": 1, "name": "Alice", "source": "api"},118 {"id": 2, "name": "Bob", "source": "api"},119 {"id": 3, "name": "Charlie", "source": "api"}120 ]121
122 def create_iterator(self) -> DataIterator:123 """Create API iterator"""124 return APIIterator(self)125
126 def get_records(self):127 """Internal method to get records"""128 return self._records129
130class APIIterator(DataIterator):131 """Iterator for API source"""132
133 def __init__(self, source: APISource):134 self._source = source135 self._records = source.get_records()136 self._index = 0137
138 def has_next(self) -> bool:139 return self._index < len(self._records)140
141 def next(self) -> Dict[str, Any]:142 if not self.has_next():143 raise StopIteration("No more records")144 record = self._records[self._index]145 self._index += 1146 return record147
148# Usage - Uniform interface for all sources!149def process_data_source(source: DataSource):150 """Process any data source uniformly"""151 iterator = source.create_iterator()152
153 print(f"Processing records from {type(source).__name__}:\n")154 while iterator.has_next():155 record = iterator.next()156 print(f" Processing: {record}")157 print()158
159def main():160 # Create different sources161 db_source = DatabaseSource("postgresql://localhost/db")162 file_source = FileSource("data.csv")163 api_source = APISource("https://api.example.com/data")164
165 # Process all sources uniformly - same code!166 process_data_source(db_source)167 process_data_source(file_source)168 process_data_source(api_source)169
170 print("✅ Iterator Pattern: Uniform interface for all data sources!")171
172if __name__ == "__main__":173 main()1import java.util.*;2
3// Step 1: Define Iterator interface4interface DataIterator {5 // Iterator interface for data sources6 boolean hasNext();7 Map<String, Object> next();8}9
10// Step 2: Define Iterable interface11interface DataSource {12 // Iterable interface for data sources13 DataIterator createIterator();14}15
16// Step 3: Implement Database Source with Iterator17class DatabaseSource implements DataSource {18 // Database data source19 private String connectionString;20 private List<Map<String, Object>> records;21
22 public DatabaseSource(String connectionString) {23 this.connectionString = connectionString;24 // Simulate database records25 this.records = new ArrayList<>();26 records.add(Map.of("id", 1, "name", "Alice", "source", "database"));27 records.add(Map.of("id", 2, "name", "Bob", "source", "database"));28 records.add(Map.of("id", 3, "name", "Charlie", "source", "database"));29 }30
31 @Override32 public DataIterator createIterator() {33 // Create database iterator34 return new DatabaseIterator(this);35 }36
37 List<Map<String, Object>> getRecords() {38 // Internal method to get records39 return records;40 }41}42
43class DatabaseIterator implements DataIterator {44 // Iterator for database source45 private DatabaseSource source;46 private List<Map<String, Object>> records;47 private int index;48
49 public DatabaseIterator(DatabaseSource source) {50 this.source = source;51 this.records = source.getRecords();52 this.index = 0;53 }54
55 @Override56 public boolean hasNext() {57 return index < records.size();58 }59
60 @Override61 public Map<String, Object> next() {62 if (!hasNext()) {63 throw new NoSuchElementException("No more records");64 }65 return records.get(index++);66 }67}68
69// Step 4: Implement File Source with Iterator70class FileSource implements DataSource {71 // File data source72 private String filepath;73 private List<String> lines;74
75 public FileSource(String filepath) {76 this.filepath = filepath;77 // Simulate file lines78 this.lines = Arrays.asList("id,name", "1,Alice", "2,Bob", "3,Charlie");79 }80
81 @Override82 public DataIterator createIterator() {83 // Create file iterator84 return new FileIterator(this);85 }86
87 List<String> getLines() {88 // Internal method to get lines89 return lines;90 }91}92
93class FileIterator implements DataIterator {94 // Iterator for file source95 private FileSource source;96 private List<String> lines;97 private int index;98
99 public FileIterator(FileSource source) {100 this.source = source;101 this.lines = source.getLines().subList(1, source.getLines().size()); // Skip header102 this.index = 0;103 }104
105 @Override106 public boolean hasNext() {107 return index < lines.size();108 }109
110 @Override111 public Map<String, Object> next() {112 if (!hasNext()) {113 throw new NoSuchElementException("No more records");114 }115 // Parse CSV line116 String line = lines.get(index++);117 String[] parts = line.split(",");118 Map<String, Object> record = new HashMap<>();119 record.put("id", Integer.parseInt(parts[0]));120 record.put("name", parts[1]);121 record.put("source", "file");122 return record;123 }124}125
126// Step 5: Implement API Source with Iterator127class APISource implements DataSource {128 // API data source129 private String endpoint;130 private List<Map<String, Object>> records;131
132 public APISource(String endpoint) {133 this.endpoint = endpoint;134 // Simulate API response135 this.records = new ArrayList<>();136 records.add(Map.of("id", 1, "name", "Alice", "source", "api"));137 records.add(Map.of("id", 2, "name", "Bob", "source", "api"));138 records.add(Map.of("id", 3, "name", "Charlie", "source", "api"));139 }140
141 @Override142 public DataIterator createIterator() {143 // Create API iterator144 return new APIIterator(this);145 }146
147 List<Map<String, Object>> getRecords() {148 // Internal method to get records149 return records;150 }151}152
153class APIIterator implements DataIterator {154 // Iterator for API source155 private APISource source;156 private List<Map<String, Object>> records;157 private int index;158
159 public APIIterator(APISource source) {160 this.source = source;161 this.records = source.getRecords();162 this.index = 0;163 }164
165 @Override166 public boolean hasNext() {167 return index < records.size();168 }169
170 @Override171 public Map<String, Object> next() {172 if (!hasNext()) {173 throw new NoSuchElementException("No more records");174 }175 return records.get(index++);176 }177}178
179// Usage - Uniform interface for all sources!180public class Main {181 public static void processDataSource(DataSource source) {182 // Process any data source uniformly183 DataIterator iterator = source.createIterator();184
185 System.out.println("Processing records from " + source.getClass().getSimpleName() + ":\n");186 while (iterator.hasNext()) {187 Map<String, Object> record = iterator.next();188 System.out.println(" Processing: " + record);189 }190 System.out.println();191 }192
193 public static void main(String[] args) {194 // Create different sources195 DatabaseSource dbSource = new DatabaseSource("postgresql://localhost/db");196 FileSource fileSource = new FileSource("data.csv");197 APISource apiSource = new APISource("https://api.example.com/data");198
199 // Process all sources uniformly - same code!200 processDataSource(dbSource);201 processDataSource(fileSource);202 processDataSource(apiSource);203
204 System.out.println("✅ Iterator Pattern: Uniform interface for all data sources!");205 }206}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:
1# External Iterator - client controls iteration2class Iterator:3 def has_next(self): pass4 def next(self): pass5
6# Client controls iteration7iterator = collection.create_iterator()8while iterator.has_next():9 item = iterator.next()10 process(item)1// External Iterator - client controls iteration2interface Iterator<T> {3 boolean hasNext();4 T next();5}6
7// Client controls iteration8Iterator<T> iterator = collection.createIterator();9while (iterator.hasNext()) {10 T item = iterator.next();11 process(item);12}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:
1# Internal Iterator - collection controls iteration2class Collection:3 def for_each(self, callback):4 for item in self._items:5 callback(item)6
7# Collection controls iteration8collection.for_each(lambda item: process(item))1// Internal Iterator - collection controls iteration2interface Collection<T> {3 void forEach(Consumer<T> callback);4}5
6// Collection controls iteration7collection.forEach(item -> process(item));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”1# ❌ Bad: Iterator exposes internal structure2class Iterator:3 def __init__(self, collection):4 self.collection = collection # Bad: Exposes collection5 self.index = 06
7 def get_collection(self): # Bad: Exposes internal structure8 return self.collection9
10# ✅ Good: Iterator hides internal structure11class Iterator:12 def __init__(self, collection):13 self._collection = collection # Good: Private14 self._index = 015
16 def has_next(self):17 return self._index < self._collection._size() # Good: Uses interface1// ❌ Bad: Iterator exposes internal structure2class Iterator {3 public Collection collection; // Bad: Exposes collection4 private int index;5
6 public Collection getCollection() { // Bad: Exposes internal structure7 return collection;8 }9}10
11// ✅ Good: Iterator hides internal structure12class Iterator {13 private Collection collection; // Good: Private14 private int index;15
16 public boolean hasNext() {17 return index < collection.size(); // Good: Uses interface18 }19}Mistake 2: Modifying Collection During Iteration
Section titled “Mistake 2: Modifying Collection During Iteration”1# ❌ Bad: Modifying collection during iteration2iterator = collection.create_iterator()3while iterator.has_next():4 item = iterator.next()5 collection.remove(item) # Bad: Modifies during iteration!6
7# ✅ Good: Collect items to remove, then remove8items_to_remove = []9iterator = collection.create_iterator()10while iterator.has_next():11 item = iterator.next()12 if should_remove(item):13 items_to_remove.append(item)14
15for item in items_to_remove:16 collection.remove(item)1// ❌ Bad: Modifying collection during iteration2Iterator<T> iterator = collection.createIterator();3while (iterator.hasNext()) {4 T item = iterator.next();5 collection.remove(item); // Bad: Modifies during iteration!6}7
8// ✅ Good: Collect items to remove, then remove9List<T> itemsToRemove = new ArrayList<>();10Iterator<T> iterator = collection.createIterator();11while (iterator.hasNext()) {12 T item = iterator.next();13 if (shouldRemove(item)) {14 itemsToRemove.add(item);15 }16}17
18for (T item : itemsToRemove) {19 collection.remove(item);20}Mistake 3: Not Handling Concurrent Modifications
Section titled “Mistake 3: Not Handling Concurrent Modifications”1# ❌ Bad: No protection against concurrent modification2class Iterator:3 def __init__(self, collection):4 self._collection = collection5 self._index = 06
7 def next(self):8 return self._collection.get(self._index) # Bad: No check!9
10# ✅ Good: Check for concurrent modification11class Iterator:12 def __init__(self, collection):13 self._collection = collection14 self._index = 015 self._expected_size = collection.size() # Good: Track size16
17 def next(self):18 if self._collection.size() != self._expected_size:19 raise ConcurrentModificationError() # Good: Detect changes20 return self._collection.get(self._index)1// ❌ Bad: No protection against concurrent modification2class Iterator {3 private Collection collection;4 private int index;5
6 public T next() {7 return collection.get(index); // Bad: No check!8 }9}10
11// ✅ Good: Check for concurrent modification12class Iterator {13 private Collection collection;14 private int index;15 private int expectedModCount; // Good: Track modifications16
17 public T next() {18 if (collection.getModCount() != expectedModCount) {19 throw new ConcurrentModificationException(); // Good: Detect changes20 }21 return collection.get(index);22 }23}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”1Client → 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”1class Iterator:2 def has_next(self): pass3 def next(self): pass4
5class Collection:6 def create_iterator(self): return Iterator()7
8iterator = collection.create_iterator()9while iterator.has_next():10 item = iterator.next()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”1class Iterator:2 def has_next(self): pass3 def next(self): pass4
5class Collection:6 def create_iterator(self): return Iterator()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! 🔄