JavaScript Iterators

The Protocol Behind `for..of`, Spread, and `Array.from`

JavaScript Iterators

An iterator is any object with a `.next()` method that returns `{ value, done }`. Learn the protocol and write your own.

5 min read Level 3/5 #iterators#iterable#protocol
What you'll learn
  • Understand the iterator and iterable protocols
  • Build a custom iterator by hand
  • Recognize built-in iterables (arrays, strings, Map, Set)

for..of, spread ([...x]), and Array.from(x) all work the same way under the hood — they ask x for an iterator, then pull values one at a time. Understanding the protocol unlocks generators, async iteration, and lazy data pipelines.

Two Protocols

There are two closely related protocols:

ProtocolThe object has…Example
IteratorA .next() method returning { value, done }The thing pulled-from
IterableA [Symbol.iterator]() method returning an iteratorThe thing you put after for..of

Most built-ins (arrays, strings, Map, Set) are iterable. Their [Symbol.iterator]() returns a fresh iterator each time.

The Iterator Manually

You almost never write this by hand, but seeing it makes the rest make sense.

Pulling values one at a time script.js
const arr = ["a", "b", "c"];
const it = arr[Symbol.iterator]();

console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
▶ Preview: console

for..of is just a loop that calls .next() until done: true.

Built-In Iterables

What you can iterate over script.js
for (const c of "abc")       console.log(c);          // "a" "b" "c"
for (const n of [1, 2, 3])   console.log(n);          // 1 2 3
for (const v of new Set([1, 1, 2])) console.log(v);   // 1 2
for (const [k, v] of new Map([["a", 1]])) console.log(k, v); // "a" 1
▶ Preview: console

Plain objects are not iterable by default — that’s why for (const x of {a: 1}) throws. Use Object.entries(obj) to get an iterable of [key, value] pairs.

Writing a Custom Iterable

The classic “make a range iterable” example.

A range you can for..of script.js
function range(start, end) {
  let i = start;
  return {
    [Symbol.iterator]() { return this; }, // I am my own iterator
    next() {
      if (i < end) return { value: i++, done: false };
      return { value: undefined, done: true };
    },
  };
}

for (const n of range(1, 4)) console.log(n);  // 1 2 3
console.log([...range(1, 4)]);                // [1, 2, 3]
▶ Preview: console

Returning this from [Symbol.iterator] is a common pattern — it means the iterable IS the iterator. The drawback: it’s single-use. Once exhausted, you’d need a new range(...) call to start over.

Iterables Power More Than for..of

const it = range(1, 4);

[...it];                    // [1, 2, 3]            spread
Array.from(it);             // [1, 2, 3]            Array.from
new Set(it);                // Set { 1, 2, 3 }      Set constructor
new Map([["a", 1]]);        // Map already iterable
const [a, b] = range(1, 4); // a=1, b=2             destructuring

Up Next

Writing iterators by hand is tedious. Generators do the boilerplate for you with a single keyword.

JavaScript Generators →