Introduction to the Iterator Pattern
Overview
The Iterator Pattern provides a way to access the elements of a collection sequentially without exposing its underlying representation. It separates the iteration logic from the collection itself, allowing multiple traversals and different iteration strategies.
The Problem
When working with logistics data, we often need to traverse collections of deliveries, routes, or packages. Without a proper abstraction, we face several challenges:
1. Exposing Internal Structure
typescript1class DeliveryRoute {2 private stops: DeliveryStop[] = [];34 // Bad: Exposes internal array5 getStops(): DeliveryStop[] {6 return this.stops;7 }8}910// Clients must know the internal structure11const route = new DeliveryRoute();12const stops = route.getStops();13for (let i = 0; i < stops.length; i++) {14 processStop(stops[i]);15}
This approach:
- Breaks encapsulation by exposing the internal array
- Couples clients to the specific collection type
- Makes it hard to change the internal implementation
- Doesn't support different traversal strategies
2. Multiple Traversal Methods
typescript1class PackageCollection {2 private packages: Package[] = [];34 // Multiple methods for different traversals5 forEachByPriority(callback: (pkg: Package) => void) { /* ... */ }6 forEachByZone(callback: (pkg: Package) => void) { /* ... */ }7 forEachByDeadline(callback: (pkg: Package) => void) { /* ... */ }8}
This leads to:
- Method explosion as traversal needs grow
- Difficulty supporting concurrent iterations
- Tight coupling between collection and iteration logic
3. Complex Traversal Logic
In logistics, we often need sophisticated iteration:
- Filter deliveries by zone or priority
- Traverse routes in optimal order
- Skip completed or cancelled items
- Iterate lazily over large datasets
The Solution: Iterator Pattern
The Iterator Pattern separates iteration logic into dedicated iterator objects:
typescript1interface Iterator<T> {2 next(): IteratorResult<T>;3 hasNext(): boolean;4}56interface IteratorResult<T> {7 value: T;8 done: boolean;9}1011interface Iterable<T> {12 createIterator(): Iterator<T>;13}
Benefits
- Encapsulation: Internal collection structure remains hidden
- Flexibility: Multiple iterators can traverse the same collection
- Separation of Concerns: Iteration logic is separate from the collection
- Polymorphism: Different collections can have different iteration strategies
- Concurrent Iterations: Multiple iterators can work independently
Pattern Structure
1┌─────────────────────────────────────────────────────────────┐2│ Iterator Pattern │3└─────────────────────────────────────────────────────────────┘45┌──────────────────┐ ┌──────────────────┐6│ <<interface>> │ │ <<interface>> │7│ Iterable<T> │ │ Iterator<T> │8├──────────────────┤ ├──────────────────┤9│ +createIterator()│◆────creates─────>│ +next() │10│ : Iterator<T> │ │ +hasNext() │11└──────────────────┘ └──────────────────┘12 △ △13 │ │14 │ │15┌───────┴──────────┐ ┌────────┴─────────┐16│ DeliveryRoute │ │ RouteIterator │17├──────────────────┤ ├──────────────────┤18│ -stops: Stop[] │──────uses───────>│ -route: Route │19│ +createIterator()│ │ -position: number│20└──────────────────┘ │ +next() │21 │ +hasNext() │22 └──────────────────┘
Key Participants
- Iterator: Defines the interface for accessing and traversing elements
- Iterable (Aggregate): Defines interface for creating an iterator
- ConcreteIterator: Implements the Iterator interface and tracks traversal state
- ConcreteIterable: Implements the Iterable interface and returns a ConcreteIterator
Logistics Context: Delivery Route Iteration
Let's see how this applies to a delivery route system:
typescript1// Iterator interface2interface RouteIterator {3 next(): IteratorResult<DeliveryStop>;4 hasNext(): boolean;5}67// Concrete Iterator8class SequentialRouteIterator implements RouteIterator {9 private position = 0;1011 constructor(private stops: DeliveryStop[]) {}1213 next(): IteratorResult<DeliveryStop> {14 if (this.hasNext()) {15 return {16 value: this.stops[this.position++],17 done: false18 };19 }20 return { value: undefined, done: true };21 }2223 hasNext(): boolean {24 return this.position < this.stops.length;25 }26}2728// Iterable interface29interface IterableRoute {30 createIterator(): RouteIterator;31}3233// Concrete Iterable34class DeliveryRoute implements IterableRoute {35 private stops: DeliveryStop[] = [];3637 addStop(stop: DeliveryStop): void {38 this.stops.push(stop);39 }4041 createIterator(): RouteIterator {42 return new SequentialRouteIterator([...this.stops]);43 }44}4546// Usage47const route = new DeliveryRoute();48route.addStop({ address: "123 Main St", zone: "A", priority: "high" });49route.addStop({ address: "456 Oak Ave", zone: "B", priority: "normal" });5051const iterator = route.createIterator();52while (iterator.hasNext()) {53 const stop = iterator.next().value;54 console.log(`Delivering to: ${stop.address}`);55}
Advantages in Logistics Context
- Different Traversal Strategies: Priority-based, zone-based, or time-optimized
- Lazy Loading: Load stops from database as needed
- Filtering: Skip cancelled or completed deliveries
- Multiple Simultaneous Iterations: Track progress of multiple drivers
- State Management: Pause and resume route traversal
TypeScript's Built-in Iterator Support
TypeScript and JavaScript provide native iterator support through the Iterable Protocol:
typescript1class DeliveryRoute implements Iterable<DeliveryStop> {2 private stops: DeliveryStop[] = [];34 addStop(stop: DeliveryStop): void {5 this.stops.push(stop);6 }78 // Implement the iterable protocol9 [Symbol.iterator](): Iterator<DeliveryStop> {10 let index = 0;11 const stops = this.stops;1213 return {14 next(): IteratorResult<DeliveryStop> {15 if (index < stops.length) {16 return { value: stops[index++], done: false };17 }18 return { value: undefined, done: true };19 }20 };21 }22}2324// Now you can use for...of25const route = new DeliveryRoute();26route.addStop({ address: "123 Main St", zone: "A", priority: "high" });2728for (const stop of route) {29 console.log(`Stop: ${stop.address}`);30}3132// Or spread operator33const allStops = [...route];3435// Or array destructuring36const [firstStop, secondStop] = route;
When to Use Iterator Pattern
Use Iterator When:
✅ You need to traverse a collection without exposing its internal structure ✅ You want to support multiple simultaneous traversals of the same collection ✅ You need different ways to traverse the same collection ✅ You want to provide a uniform interface for traversing different collections ✅ You need lazy evaluation or filtering during iteration
Consider Alternatives When:
❌ Simple array access is sufficient ❌ You only need one way to traverse the collection ❌ The collection is always traversed completely ❌ Performance of iterator object creation is a concern
Real-World Logistics Examples
1. Package Sorting Facility
typescript1// Iterate through packages by conveyor belt priority2for (const package of sortingFacility.getPackagesByPriority()) {3 assignToConveyor(package);4}
2. Fleet Management
typescript1// Iterate through available vehicles by proximity to depot2for (const vehicle of fleet.getVehiclesByProximity(depot)) {3 if (vehicle.hasCapacity()) {4 assignDelivery(vehicle);5 break;6 }7}
3. Route Optimization
typescript1// Iterate through stops in optimized order2for (const stop of route.getOptimizedStops()) {3 calculateETA(stop);4}
Common Variations
1. External Iterator
The client controls the iteration:
typescript1const iterator = route.createIterator();2while (iterator.hasNext()) {3 const stop = iterator.next().value;4 // Client controls when to get next element5}
2. Internal Iterator
The collection controls the iteration:
typescript1route.forEach((stop) => {2 // Collection calls this function for each element3});
3. Filtering Iterator
typescript1class PriorityRouteIterator implements RouteIterator {2 constructor(3 private stops: DeliveryStop[],4 private priority: string5 ) {}67 next(): IteratorResult<DeliveryStop> {8 while (this.position < this.stops.length) {9 const stop = this.stops[this.position++];10 if (stop.priority === this.priority) {11 return { value: stop, done: false };12 }13 }14 return { value: undefined, done: true };15 }16}
Key Takeaways
- Iterator Pattern separates traversal logic from collections
- It provides a uniform interface for accessing elements sequentially
- Multiple iterators can traverse the same collection independently
- TypeScript's Symbol.iterator provides native iterator support
- Iterators enable lazy evaluation and filtering during traversal
- The pattern encapsulates collection internals while allowing traversal
Next Steps
In the next lesson, we'll dive deep into TypeScript's iterator protocol, exploring:
- The
IterableandIteratorinterfaces - Using
Symbol.iterator - Generator functions for creating iterators
- Advanced iteration patterns
Understanding the Iterator pattern is essential for working with collections in a flexible, maintainable way while keeping your logistics system's internal structure hidden and protected.