Advance Javascript series: Iterables

Hamza Iqbal
4 min readFeb 28, 2023

--

As a language, JavaScript is built on the concept of iteration. The ability to repeat a task or action over a set of data is fundamental to many of the operations we perform in programming. In JavaScript, the concept of iteration is implemented using the concept of iterables.

Iterables are data structures that allow for sequential access to their elements by other data consumers. For instance, a data structure that unloads data one-by-one in order when placed in a for...of loop.

The iterable protocol consists of the iterable, which is the data structure itself, and the iterator, a pointer that moves over the iterable. When an array is placed in a for...of loop, its iterable property, named Symbol.iterator, returns an iterator. This object can be used on a shared interface used by all looping control structures.

Iterating over arrays

One of the most common uses of iterables in JavaScript is to iterate over arrays. Arrays are collections of data that can be indexed by a numerical index. The following example shows how to iterate over an array of numbers using a for...of loop:

const numbers = [1, 2, 3, 4, 5];

for (const number of numbers) {
console.log(number);
}

In this example, the for...of loop iterates over the numbers array, assigning each element in turn to the number variable. The loop then logs each number to the console.

Iterating over strings

Strings are another common iterable in JavaScript. A string can be thought of as an array of characters. The following example shows how to iterate over a string using a for...of loop:

const str = "hello";

for (const char of str) {
console.log(char);
}

In this example, the for...of loop iterates over the str string, assigning each character in turn to the char variable. The loop then logs each character to the console.

Iterating over sets

Sets are a relatively new data structure in JavaScript, added in ECMAScript 6. Sets are similar to arrays in that they are collections of data, but they have some important differences. One of these differences is that sets do not allow duplicates. The following example shows how to iterate over a set using a for...of loop:

const mySet = new Set([1, 2, 3]);

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

In this example, the for...of loop iterates over the mySet set, assigning each value in turn to the value variable. The loop then logs each value to the console.

Creating custom iterables

While many built-in JavaScript data structures are iterables, it is also possible to create custom iterables. To create a custom iterable, an object must implement the Symbol.iterator method. This method must return an iterator object that provides a next() method.

The following example shows how to create a custom iterable that generates the Fibonacci sequence:

const fibonacci = {
[Symbol.iterator]() {
let a = 0, b = 1;
return {
next() {
const value = a;
a = b;
b = value + b;
return { value, done: false };
}
}
}
}

for (const number of fibonacci) {
console.log(number);
if (number > 1000) {
break;
}
}

In this example, the fibonacci object is a custom iterable that generates the Fibonacci sequence. The Symbol.iterator method returns an iterator object that generates the sequence. The next() method of the iterator object returns the next value in the sequence each time it is called.

Symbol.iterator

We can easily grasp the concept of iterables by making one of our own.

For instance, we have an object that is not an array, but looks suitable for for..of.

Like a range object that represents an interval of numbers:

let range = {
from: 1,
to: 5
};

To make the range object iterable (and thus let for..of work) we need to add a method to the object named Symbol.iterator (a special built-in symbol just for that).

  1. When for..of starts, it calls that method once (or errors if not found). The method must return an iterator – an object with the method next.
  2. Onward, for..of works only with that returned object.
  3. When for..of wants the next value, it calls next() on that object.
  4. The result of next() must have the form {done: Boolean, value: any}, where done=true means that the loop is finished, otherwise value is the next value.

Here’s the full implementation for range with remarks:

let range = {
from: 1,
to: 5
};

// 1. call to for..of initially calls this
range[Symbol.iterator] = function() {

// ...it returns the iterator object:
// 2. Onward, for..of works only with the iterator object below, asking it for next values
return {
current: this.from,
last: this.to,

// 3. next() is called on each iteration by the for..of loop
next() {
// 4. it should return the value as an object {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};

// now it works!
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5

Please note the core feature of iterables: separation of concerns.

  • The range itself does not have the next() method.
  • Instead, another object, a so-called “iterator” is created by the call to range[Symbol.iterator](), and its next() generates values for the iteration.

So, the iterator object is separate from the object it iterates over.

--

--

Hamza Iqbal
Hamza Iqbal

Written by Hamza Iqbal

Software engineer (MERN Stack)

No responses yet