Sebastian Gomez
Understanding the Iterator Pattern in JavaScript
In this post we'll learn how to implement the Iterator pattern in JavaScript and in which situations we can use it.
The Iterator pattern is one of the most widely used software design patterns in JavaScript, and also one of the simplest. It's a behavioral pattern, since it defines how objects communicate with each other. Important applications stem from it that can help define better architectures in web applications, which is why studying and using it is highly recommended.
Let's start by defining this pattern. Basically, we've all used arrays like this:
[1, 2, 3, 4]And usually, if we want to traverse one, we quickly reach for a loop structure like for or while. However, that makes the code a bit more imperative and leaves us with less control over each individual item inside the array. This is where the Iterator pattern starts to shine, because it gives us an orderly way to traverse the collection where we decide when we want the next object and when we don't. To achieve this, three methods must exist within the iterator:
first()Always returns the first object in the collection.next()Returns the next object in the collection if it exists.current()Returns the current object in the collection that we're standing on.
So let's start by defining a class called Iterator with these three methods:
// Define the "Iterator" class
class Iterator {
// Constructor of the "Iterator" class that accepts a "collection" argument
constructor(collection) {
// Initialize the "index" property with the value 0
this.index = 0;
// Assign the "collection" argument to the object's "collection" property
this.collection = collection;
}
// "first" method to get the first element of the collection
first() {
// Return the first element of the collection (index 0)
return this.collection[0];
}
// "next" method to get the next element of the collection
next() {
// Increase the "index" property by 1
this.index += 1;
// Return the element of the collection at the current "index" position
return this.collection[this.index];
}
// "current" method to get the current element of the collection
current() {
// Return the element of the collection at the current "index" position
return this.collection[this.index];
}
}As you can see, using ES6 we've defined a class that performs very basic operations on the array while always keeping track of the index the collection is standing on at any given moment. This lets us move across it easily. Additionally, although it's not required by the pattern's implementation, it is highly recommended to add two more utility methods to the iterator class. These are:
hasNext()Returnstrueif there are more elements available in the collection.reset()Lets us reset the index to iterate over the collection again.
Let's see what the implementation would look like:
// Define the "Iterator" class
class Iterator {
// ...
// "reset" method to reset the iterator's index to the start of the collection
reset() {
// Assign the value 0 to the object's "index" property
this.index = 0;
}
// "hasNext" method to check whether there's a next element in the collection
hasNext() {
// Return "true" if the current index plus one is less than the collection length, otherwise return "false"
return this.index + 1 < this.collection.length;
}
// ...
}Super simple. Once your class is defined, you can use practically any kind of array to build your iterator and operate on it. Let's see a practical example of its use with a while loop:
// Create a constant "arr" containing an array with the numbers 1 to 5
const arr = [1, 2, 3, 4, 5];
// Create a new instance of the "Iterator" class using the array "arr" as the argument and assign the resulting object to the constant "arrayIterator"
const arrayIterator = new Iterator(arr);
// Print the first element of the array to the console using the "first" method of the "arrayIterator" object
console.log(arrayIterator.first());
// Start a "while" loop that runs while there's a next element in the array
while (arrayIterator.hasNext()) {
// Print the next element of the array to the console using the "next" method of the "arrayIterator" object
console.log(arrayIterator.next());
}This completes the Iterator pattern. An honest clarification: this while example is still imperative, we drive the loop by hand. What the pattern gives us isn't declarative magic, but a uniform interface (first, next, current, hasNext) for traversing any collection without the consuming code needing to know how it's built internally. Later on we'll see how JavaScript turns that same idea into something that does read declaratively with for...of.
Below you can see all the complete code for this example:
// Define the "Iterator" class
class Iterator {
// Constructor of the "Iterator" class that accepts a "collection" argument
constructor(collection) {
// Initialize the "index" property with the value 0
this.index = 0;
// Assign the "collection" argument to the object's "collection" property
this.collection = collection;
}
// "first" method to get the first element of the collection
first() {
// Return the first element of the collection (index 0)
return this.collection[0];
}
// "next" method to get the next element of the collection
next() {
// Increase the "index" property by 1
this.index += 1;
// Return the element of the collection at the current "index" position
return this.collection[this.index];
}
// "current" method to get the current element of the collection
current() {
// Return the element of the collection at the current "index" position
return this.collection[this.index];
}
// "reset" method to reset the iterator's index to the start of the collection
reset() {
// Assign the value 0 to the object's "index" property
this.index = 0;
}
// "hasNext" method to check whether there's a next element in the collection
hasNext() {
// Return "true" if the current index plus one is less than the collection length, otherwise return "false"
return this.index + 1 < this.collection.length;
}
}
// Create a constant "arr" containing an array with the numbers 1 to 5
const arr = [1, 2, 3, 4, 5];
// Create a new instance of the "Iterator" class using the array "arr" as the argument and assign the resulting object to the constant "arrayIterator"
const arrayIterator = new Iterator(arr);
// Print the first element of the array to the console using the "first" method of the "arrayIterator" object
console.log(arrayIterator.first());
// Start a "while" loop that runs while there's a next element in the array
while (arrayIterator.hasNext()) {
// Print the next element of the array to the console using the "next" method of the "arrayIterator" object
console.log(arrayIterator.next());
}The Iterator pattern already lives inside JavaScript
Something important to know: JavaScript ships its own version of this pattern built into the language, known as the iteration protocol. Arrays, Strings, Sets, and Maps are already iterable natively, which is why we can traverse them directly with for...of:
const arr = [1, 2, 3, 4, 5];
// for...of consumes the array's native iterator for us
for (const value of arr) {
console.log(value);
}Here we don't write any while loop or manage indexes by hand: this does read declaratively, we say what we want (visit every value) and not how to advance step by step. The natural question is: how does for...of know how to traverse an array? The answer is that it looks for a special method under the Symbol.iterator key. Any object that implements [Symbol.iterator]() and returns an object with a next() method (returning { value, done }) becomes iterable.
We can rewrite our class so it honors that contract and integrates with the rest of the language:
class Collection {
constructor(items) {
this.items = items;
}
// By implementing Symbol.iterator, our class becomes natively iterable
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
// The protocol requires a next() method returning { value, done }
next() {
if (index < items.length) {
return { value: items[index++], done: false };
}
return { value: undefined, done: true };
},
};
}
}
const collection = new Collection([1, 2, 3, 4, 5]);
// Now we can use for...of directly on our instance
for (const value of collection) {
console.log(value);
}The most convenient way to produce that iterator is with a generator, a function declared with function* that hands out values with yield. The generator builds the { value, done } object for us, so the code becomes much shorter:
class Collection {
constructor(items) {
this.items = items;
}
// A generator implements the iteration protocol for us
*[Symbol.iterator]() {
for (const item of this.items) {
// Each yield hands out the next value in the collection
yield item;
}
}
}
const collection = new Collection([1, 2, 3, 4, 5]);
for (const value of collection) {
console.log(value);
}The takeaway is clear: the Iterator class we wrote by hand at the start is an excellent way to understand the pattern from the inside, but in modern JavaScript you'll almost always want to lean on Symbol.iterator, generators, and for...of, because that way your collections work with everything that already expects an iterable: destructuring, the spread operator ..., Array.from(), and much more.
Note: The iteration protocol is stable and has been part of the JavaScript standard since ES6, so you can use it confidently both in the browser and in Node.js.
Leave me a comment if you managed to implement it, if you want to add some other functionality, or if you have any questions. And remember, if you liked it, you can also share it using the social links below.
Conclusions
- The Iterator pattern is an orderly way to traverse collections of objects that gives us a uniform interface and greater control over the process.
- The implementation is simple and direct, and its usefulness is almost immediate.
- Adding utility methods like
hasNext()andreset()further improves the flexibility of our implementation. - JavaScript already ships this pattern built in through
Symbol.iterator, generators, andfor...of, which is the idiomatic way to apply it today.
Exercises to practice
- Traverse
SetandMapobjects in JavaScript usingfor...ofand compare the experience with our hand made class. - Extend the
Iteratorclass (or the generator) so it can apply functions to the elements of the collection as they are traversed, for example filtering or mapping elements. - Build an application that uses the Iterator pattern to traverse and process data from an API or a database.
3-point summary
- The Iterator pattern lets us traverse collections of objects with a uniform interface and greater control.
- The basic implementation in JavaScript requires the
first(),next(), andcurrent()methods, and it's a good idea to add utilities likehasNext()andreset(). - In modern JavaScript this pattern is already built into the language with
Symbol.iterator, generatorsfunction*, andfor...of.
That's all. I hope this post is useful to you and that you can apply it to a project you have in mind, and that it helped you understand the nature of the Iterator pattern in JavaScript. If you have questions, comments, or suggestions, feel free to leave a comment below. Good luck with your projects and keep learning!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.