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 [email protected]. I’d greatly appreciate your feedback! :)