lesson

Introduction to the Iterator Pattern

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

typescript
1class DeliveryRoute {
2 private stops: DeliveryStop[] = [];
3
4 // Bad: Exposes internal array
5 getStops(): DeliveryStop[] {
6 return this.stops;
7 }
8}
9
10// Clients must know the internal structure
11const 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

typescript
1class PackageCollection {
2 private packages: Package[] = [];
3
4 // Multiple methods for different traversals
5 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:

typescript
1interface Iterator<T> {
2 next(): IteratorResult<T>;
3 hasNext(): boolean;
4}
5
6interface IteratorResult<T> {
7 value: T;
8 done: boolean;
9}
10
11interface Iterable<T> {
12 createIterator(): Iterator<T>;
13}

Benefits

  1. Encapsulation: Internal collection structure remains hidden
  2. Flexibility: Multiple iterators can traverse the same collection
  3. Separation of Concerns: Iteration logic is separate from the collection
  4. Polymorphism: Different collections can have different iteration strategies
  5. Concurrent Iterations: Multiple iterators can work independently

Pattern Structure

1┌─────────────────────────────────────────────────────────────┐
2│ Iterator Pattern │
3└─────────────────────────────────────────────────────────────┘
4
5┌──────────────────┐ ┌──────────────────┐
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

  1. Iterator: Defines the interface for accessing and traversing elements
  2. Iterable (Aggregate): Defines interface for creating an iterator
  3. ConcreteIterator: Implements the Iterator interface and tracks traversal state
  4. 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:

typescript
1// Iterator interface
2interface RouteIterator {
3 next(): IteratorResult<DeliveryStop>;
4 hasNext(): boolean;
5}
6
7// Concrete Iterator
8class SequentialRouteIterator implements RouteIterator {
9 private position = 0;
10
11 constructor(private stops: DeliveryStop[]) {}
12
13 next(): IteratorResult<DeliveryStop> {
14 if (this.hasNext()) {
15 return {
16 value: this.stops[this.position++],
17 done: false
18 };
19 }
20 return { value: undefined, done: true };
21 }
22
23 hasNext(): boolean {
24 return this.position < this.stops.length;
25 }
26}
27
28// Iterable interface
29interface IterableRoute {
30 createIterator(): RouteIterator;
31}
32
33// Concrete Iterable
34class DeliveryRoute implements IterableRoute {
35 private stops: DeliveryStop[] = [];
36
37 addStop(stop: DeliveryStop): void {
38 this.stops.push(stop);
39 }
40
41 createIterator(): RouteIterator {
42 return new SequentialRouteIterator([...this.stops]);
43 }
44}
45
46// Usage
47const route = new DeliveryRoute();
48route.addStop({ address: "123 Main St", zone: "A", priority: "high" });
49route.addStop({ address: "456 Oak Ave", zone: "B", priority: "normal" });
50
51const iterator = route.createIterator();
52while (iterator.hasNext()) {
53 const stop = iterator.next().value;
54 console.log(`Delivering to: ${stop.address}`);
55}

Advantages in Logistics Context

  1. Different Traversal Strategies: Priority-based, zone-based, or time-optimized
  2. Lazy Loading: Load stops from database as needed
  3. Filtering: Skip cancelled or completed deliveries
  4. Multiple Simultaneous Iterations: Track progress of multiple drivers
  5. State Management: Pause and resume route traversal

TypeScript's Built-in Iterator Support

TypeScript and JavaScript provide native iterator support through the Iterable Protocol:

typescript
1class DeliveryRoute implements Iterable<DeliveryStop> {
2 private stops: DeliveryStop[] = [];
3
4 addStop(stop: DeliveryStop): void {
5 this.stops.push(stop);
6 }
7
8 // Implement the iterable protocol
9 [Symbol.iterator](): Iterator<DeliveryStop> {
10 let index = 0;
11 const stops = this.stops;
12
13 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}
23
24// Now you can use for...of
25const route = new DeliveryRoute();
26route.addStop({ address: "123 Main St", zone: "A", priority: "high" });
27
28for (const stop of route) {
29 console.log(`Stop: ${stop.address}`);
30}
31
32// Or spread operator
33const allStops = [...route];
34
35// Or array destructuring
36const [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

typescript
1// Iterate through packages by conveyor belt priority
2for (const package of sortingFacility.getPackagesByPriority()) {
3 assignToConveyor(package);
4}

2. Fleet Management

typescript
1// Iterate through available vehicles by proximity to depot
2for (const vehicle of fleet.getVehiclesByProximity(depot)) {
3 if (vehicle.hasCapacity()) {
4 assignDelivery(vehicle);
5 break;
6 }
7}

3. Route Optimization

typescript
1// Iterate through stops in optimized order
2for (const stop of route.getOptimizedStops()) {
3 calculateETA(stop);
4}

Common Variations

1. External Iterator

The client controls the iteration:

typescript
1const iterator = route.createIterator();
2while (iterator.hasNext()) {
3 const stop = iterator.next().value;
4 // Client controls when to get next element
5}

2. Internal Iterator

The collection controls the iteration:

typescript
1route.forEach((stop) => {
2 // Collection calls this function for each element
3});

3. Filtering Iterator

typescript
1class PriorityRouteIterator implements RouteIterator {
2 constructor(
3 private stops: DeliveryStop[],
4 private priority: string
5 ) {}
6
7 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

  1. Iterator Pattern separates traversal logic from collections
  2. It provides a uniform interface for accessing elements sequentially
  3. Multiple iterators can traverse the same collection independently
  4. TypeScript's Symbol.iterator provides native iterator support
  5. Iterators enable lazy evaluation and filtering during traversal
  6. 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 Iterable and Iterator interfaces
  • 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.