Skip to main content

JavaScript Iterators

Introduction​

Iterators are one of the powerful features introduced in ES6 (ECMAScript 2015) that provide a standard way to access elements in a collection one at a time. They enable you to traverse through data structures like arrays, strings, maps, sets, and even custom objects in a consistent manner.

At their core, iterators follow a simple principle: they provide a method called next() that returns an object with two properties:

  • value: the current element
  • done: a boolean indicating whether iteration is complete

Let's dive deeper into understanding how iterators work and how you can leverage them in your JavaScript applications.

The Iterator Protocol​

The iterator protocol defines how to produce a sequence of values from an object. An object becomes an iterator when it implements a next() method with the correct behavior.

Here's a simple example of a basic iterator:

javascript
// Creating a simple iterator
function createCounterIterator(start, end) {
let count = start;

return {
next() {
if (count <= end) {
return { value: count++, done: false };
} else {
return { done: true };
}
}
};
}

// Using the iterator
const counter = createCounterIterator(1, 5);

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

In this example, we've created a simple counter iterator that counts from a start value to an end value. Each time we call next(), we get the current count as the value, and the counter increments.

Iterable Objects​

An iterable is an object that implements the iterable protocol by having a method with the key Symbol.iterator that returns an iterator. Many built-in types in JavaScript are already iterable:

  • Arrays
  • Strings
  • Maps
  • Sets
  • DOM NodeLists

Here's how to create a custom iterable object:

javascript
// Creating a custom iterable object
const customIterable = {
data: [10, 20, 30, 40, 50],

// The Symbol.iterator method that returns an iterator
[Symbol.iterator]() {
let index = 0;

return {
// The iterator object with next() method
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};

// Using the iterable with for...of
for (const item of customIterable) {
console.log(item);
}
// Output:
// 10
// 20
// 30
// 40
// 50

// Using the spread operator with the iterable
const values = [...customIterable];
console.log(values); // [10, 20, 30, 40, 50]

The for...of Loop​

The for...of loop was introduced alongside iterators and iterables. It provides a clean way to loop over iterable objects:

javascript
const fruits = ['Apple', 'Banana', 'Orange', 'Mango'];

// Using for...of loop
for (const fruit of fruits) {
console.log(fruit);
}
// Output:
// Apple
// Banana
// Orange
// Mango

The for...of loop calls the Symbol.iterator method behind the scenes and then repeatedly calls the next() method on the resulting iterator until the iteration is complete.

Built-in Iterables​

Let's look at how iterators work with different built-in types:

String Iteration​

javascript
const message = "Hello";
for (const char of message) {
console.log(char);
}
// Output:
// H
// e
// l
// l
// o

Map Iteration​

javascript
const userRoles = new Map([
['John', 'Admin'],
['Sarah', 'Editor'],
['Mike', 'Subscriber']
]);

for (const [user, role] of userRoles) {
console.log(`${user} is a ${role}`);
}
// Output:
// John is a Admin
// Sarah is a Editor
// Mike is a Subscriber

Set Iteration​

javascript
const uniqueColors = new Set(['red', 'blue', 'green', 'red']);

for (const color of uniqueColors) {
console.log(color);
}
// Output:
// red
// blue
// green

Generator Functions: Simplified Iterators​

Generator functions provide an easier way to create iterators. They use the function* syntax and yield keyword to define iterative algorithms in a simpler and more readable manner.

javascript
function* countUp(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}

const counter = countUp(1, 5);

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

// Or using a for...of loop
for (const num of countUp(1, 5)) {
console.log(num);
}
// Output:
// 1
// 2
// 3
// 4
// 5

Practical Examples​

Example 1: Creating a Pagination Iterator​

javascript
function* paginate(array, pageSize) {
for (let i = 0; i < array.length; i += pageSize) {
yield array.slice(i, i + pageSize);
}
}

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const pages = paginate(data, 4);

console.log(pages.next().value); // [1, 2, 3, 4]
console.log(pages.next().value); // [5, 6, 7, 8]
console.log(pages.next().value); // [9, 10, 11, 12]
console.log(pages.next().done); // true

Example 2: Creating an Iterator for a Tree Structure​

javascript
// A tree node structure
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}

// Create a sample tree
const root = new TreeNode(10);
root.left = new TreeNode(5);
root.right = new TreeNode(15);
root.left.left = new TreeNode(3);
root.left.right = new TreeNode(7);
root.right.left = new TreeNode(12);
root.right.right = new TreeNode(18);

// In-order traversal using generator
function* inOrderTraversal(node) {
if (node) {
yield* inOrderTraversal(node.left);
yield node.value;
yield* inOrderTraversal(node.right);
}
}

// Use the iterator to traverse the tree
for (const value of inOrderTraversal(root)) {
console.log(value);
}
// Output:
// 3
// 5
// 7
// 10
// 12
// 15
// 18

Example 3: Generating an Infinite Sequence​

javascript
// Generate Fibonacci numbers infinitely
function* fibonacciGenerator() {
let [a, b] = [0, 1];

while (true) {
yield a;
[a, b] = [b, a + b];
}
}

// Get the first 10 Fibonacci numbers
const fib = fibonacciGenerator();
const first10 = [];

for (let i = 0; i < 10; i++) {
first10.push(fib.next().value);
}

console.log(first10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Summary​

JavaScript iterators provide a powerful and consistent way to traverse various data structures. They work hand-in-hand with iterables and the for...of loop to create a standardized iteration protocol in JavaScript.

Key takeaways:

  • Iterators implement a next() method that returns { value, done }
  • Iterables implement Symbol.iterator which returns an iterator
  • Generator functions provide an easy way to create iterators
  • Many built-in objects like arrays, strings, maps, and sets are already iterable
  • Iterators enable powerful patterns like lazy evaluation and infinite sequences

Practice Exercises​

  1. Create an iterator that generates all even numbers up to a specified limit.
  2. Build a custom iterable object that represents a classroom with students.
  3. Write a generator function that yields the reverse of a given array.
  4. Create an iterator that traverses through a nested object structure.
  5. Implement a "range" function similar to Python's range using iterators.

Additional Resources​



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)