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.
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:
| Protocol | The object has… | Example |
|---|---|---|
| Iterator | A .next() method returning { value, done } | The thing pulled-from |
| Iterable | A [Symbol.iterator]() method returning an iterator | The 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.
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 } for..of is just a loop that calls .next() until done: true.
Built-In Iterables
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 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.
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] 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 →