Iterators in JavaScript

What is an Iterator?

From latin iterat meaning “repeated” an Iterator provides functionality that allows you to go through (iterate over) each item in a collection of objects.

There are many ways to iterate over a collection of items:

  • You can use for loops
  • You can use methods such as map() and filter()
  • You can use Iterators

Why Iterators?

You are already familiar with using for loops and methods, so now you ask yourself why you should even consider Iterators? Here are the reasons:

  • Iterators are the underlying principles when using for loops. Everytime you use a for loop you actually invoke the Iterator under the hood.
  • Because an Iterator allows you to process each item one at a time, giving you the opportunity to stop looping and continue later on – that is something that an ordinary loop cannot do out of the box.
  • Not all Iterators are expressed as arrays. Arrays must be allocated in their entirety, but iterators are consumed only as necessary. Because of this, iterators can express sequences of unlimited size.

Iterator protocol

Technically, an Iterator is any object that has a next() method that returns a value and done (boolean) property, two requirements of a specification also known as Iterator protocol. Calling next() is also referred to as to consume the iterator, because it is generally only possible to do once.

What is an Iterable?

An Iterable is a thing that you can iterate over, such as an Array, String, Map and a Set. Technically, an Iterable must implement @@iterator method or in other words: The object must have a property with a Symbol.iterator key.

Create custom Iterable

const myIterable = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
}

for (let value of myIterable) { 
    console.log(value); 
}
// 1
// 2
// 3

or

[...myIterable]; // [1, 2, 3]

For..of loop only works with Iterable objects, it iterates over values. Don’t mix that up with for..in loops which iterates over enumberable properties of an object. That’s the important difference.

Iterating over an Array

Using for..of

const arr = [1,2,3,4];

for (const value of arr) {
  console.log(value);
}

Instead of using for..of we can also iterate by using the Array’s Iterator:

const arr = [1,2,3,4];
const it = arr[Symbol.iterator]();

console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: 4, done: false}
console.log(it.next()); // {value: undefined, done: true}

Iterating over a Map

Again, we use for..of to iterate over the map.

const map = new Map();
map.set("key1", "value 1");
map.set("key2", "value 2");

for(const [key, value] of map) {
  console.log(`${key} and ${value}`);
}

And here is how we use the Iterator to iterate the map:

const map = new Map();
map.set("key1", "value1");
map.set("key2", "value2");

const mapIterator = map[Symbol.iterator]();
console.log(mapIterator.next());
console.log(mapIterator.next());
console.log(mapIterator.next());

results in:

Object {
  done: false,
  value: ["key1", "value1"]
}

Object {
  done: false,
  value: ["key2", "value2"]
}

Object {
  done: true,
  value: undefined
}

Iterating over a Set

This example shows how to iterate over a Set using for..of:

const set = new Set();
set.add("Apples");
set.add("Bananas");

for (value of set) {
  console.log(`${value}`);
}

This example shows how to iterate over a Set using the Iterator:

const set = new Set();
set.add("Apples");
set.add("Bananas");

const setIterator = set[Symbol.iterator]();
console.log(setIterator.next());
console.log(setIterator.next());
console.log(setIterator.next());
Object {
  done: false,
  value: "Apples"
}

Object {
  done: false,
  value: "Bananas"
}

Object {
  done: true,
  value: undefined
}

Create a custom Iterator

The naive approach

Naive, because it work’s and it is easy to understand, but it is not the recommended way. Let’s create a simple Iterator fulfilling the specifications mentioned above, which is having a next() method and returning an object with value and done property:

function myIterator(start, finish) {
  let index = start;
  let count = 0;
  
  return {
    next() {
      let result = {
        value: count,
        done: true
      };
      
      if(index < finish) {
        result = {value: index, done: false};
        index += 1;
        count++;
      }
      
      return result;
    }
  }
}

const it = myIterator(0, 8);
let res = it.next();

while(!res.done) {
  console.log(res.value);
  res = it.next();
}

The previous example is indeed an Iterator, but it is not the recommended way to create one. Why? Because there is no way of knowing that myIterator is an Iterator without looking at the code. Instead you should create Iterators from Iterable objects.

This will output the numbers 0 to 7.

What do Iterators have to do with Generators?

Custom iterators are a useful tool, but their creation requires careful programming because you explicitly have to maintain their internal state. Generator functions provide a powerful alternative: they allow you to define a single function whose execution is not continuous. More about Generators in the next article.

About Author

Mathias Bothe To my job profile

I am Mathias from Heidelberg, Germany. I am a passionate IT freelancer with 15+ years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I create Bosycom and initiated several software projects.