Skip to content

Iterator Design Pattern

Video Lecture

Section Video Links
Iterator Pattern Iterator Iterator Pattern  
Iterator Use Case Iterator Use Case Iterator Use Case  

Overview

The Iterator will commonly contain two methods that perform the following concepts.

  • next: returns the next object in the aggregate (collection, object).
  • hasNext: returns a Boolean indicating if the Iterable is at the end of the iteration or not.

The benefits of using the Iterator pattern are that the client can traverse a collection of aggregates(objects) without needing to understand their internal representations and/or data structures.

Terminology

  • Iterator Interface: The Interface for an object to implement.
  • Concrete Iterator: (Iterable) The instantiated object that implements the iterator and contains a collection of aggregates.
  • Aggregate Interface: An interface for defining an aggregate (object).
  • Concrete Aggregate: The object that implements the Aggregate interface.

Iterator UML Diagram

Iterator Pattern Overview

Source Code

In this concept example, I create 4 objects called Aggregate and group them into a collection.

They are very minimal objects that implement one method that prints a line.

I then create an Iterable and pass in the collection of Aggregates.

I can now traverse the aggregates through the Iterable interface.

./src/iterator/iterator-concept.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// The Iterator Pattern Concept

interface IIterator {
    next(): IAggregate
    // Return the object in collection

    hasNext(): boolean
    // Returns Boolean whether at end of collection or not
}

class IteratorConcept implements IIterator {
    // The concrete iterator (iterable)
    index: number
    aggregates: IAggregate[]

    constructor(aggregates: IAggregate[]) {
        this.index = 0
        this.aggregates = aggregates
    }

    next() {
        if (this.index < this.aggregates.length) {
            const aggregate = this.aggregates[this.index]
            this.index += 1
            return aggregate
        }
        throw new Error('At End of Iterator')
    }

    hasNext() {
        return this.index < this.aggregates.length
    }
}

interface IAggregate {
    // An interface that the aggregates should implement
    method(): void
}

class Aggregate implements IAggregate {
    // A concrete object
    method(): void {
        console.log('This method has been invoked')
    }
}

// The Client
const AGGREGATES = [
    new Aggregate(),
    new Aggregate(),
    new Aggregate(),
    new Aggregate(),
]

// AGGREGATES is an array that is already iterable by default.
// but we can create own own iterator on top anyway.
const ITERABLE = new IteratorConcept(AGGREGATES)

while (ITERABLE.hasNext()) {
    ITERABLE.next().method()
}

Output

node ./dist/iterator/iterator-concept.js
This method has been invoked
This method has been invoked
This method has been invoked
This method has been invoked

Iterator Use Case

The iterator in this brief example will return the next number in the iterator multiplied by 2 modulus 11. It dynamically creates the returned object (number) at runtime.

It has no hasNext() method since the result is modulated by 11, that will loop the results no matter how large the iterator index is. Furthermore, it will also appear to alternate between a series of even numbers and odd numbers.

Also, just to demonstrate that implementing abstract classes and interfaces is not always necessary, this example uses no abstract base classes or interfaces.

Example UML Diagram

Iterator Pattern Overview

Source Code

./src/iterator/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// The Iterator Pattern Concept

class NumberWheel {
    // The concrete iterator (iterable)

    index: number

    constructor() {
        this.index = 0
    }

    next() {
        // Return a new number next in the wheel
        this.index = this.index + 1
        return (this.index * 2) % 11
    }
}

// The Client
const NUMBERWHEEL = new NumberWheel()

for (let i = 0; i < 22; i++) {
    process.stdout.write(NUMBERWHEEL.next() + ' ')
}

Output

node ./dist/iterator/client.js
2 4 6 8 10 1 3 5 7 9 0 2 4 6 8 10 1 3 5 7 9 0

Summary

  • Use an iterator when you need to traverse over a collection, or you want an object that can output a series of dynamically created objects.
  • At minimum, an iterator needs a next equivalent method that returns an object.
  • Optionally you can also create a helper function that indicates whether an iterator is at the end or not. This is useful if you use your iterator in a while loop.