Iterators & Generators
The iteration protocol, custom iterables, generator functions, and lazy sequences in JavaScript.
Iterators, iterables, and generator functions
You've used for...of loops to iterate over arrays and strings.
But how does JavaScript know how to loop over them? The answer is the
iteration protocol — a contract that any object can implement to
become "loopable." Generator functions make implementing this contract easy.
The iteration protocol
An object is iterable if it has a [Symbol.iterator]()
method that returns an iterator — an object with a next()
method that returns { value, done } pairs.
// Arrays are built-in iterables:
const arr = [10, 20, 30];
// for...of uses the iterator protocol under the hood:
for (const n of arr) {
console.log(n); // 10, 20, 30
}
// You can call the protocol manually:
const iter = arr[Symbol.iterator]();
console.log(iter.next()); // { value: 10, done: false }
console.log(iter.next()); // { value: 20, done: false }
console.log(iter.next()); // { value: 30, done: false }
console.log(iter.next()); // { value: undefined, done: true }Strings, Maps, Sets, NodeLists, and arguments objects are all built-in iterables.
That's why for...of, spread (...), destructuring, and
Array.from() all work on them.
Generator functions — the easy way to make iterables
Writing the iterator protocol by hand is verbose. Generator functions
(function*) do it for you. A generator can yield a value
and then pause — it resumes on the next .next() call.
// function* marks a generator function
function* countdown(from) {
while (from > 0) {
yield from; // ← pause here, return 'from', wait for next .next()
from--;
}
yield "Go!";
}
// A generator is automatically an iterable:
for (const value of countdown(3)) {
console.log(value); // 3, 2, 1, Go!
}
// Spread works too:
console.log([...countdown(3)]); // [3, 2, 1, "Go!"]A range generator — the most useful first generator
// Python has range(). JS doesn't — but you can write it:
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
for (const n of range(1, 5)) {
console.log(n); // 1, 2, 3, 4, 5
}
console.log([...range(0, 10, 2)]); // [0, 2, 4, 6, 8, 10]
// Works in destructuring:
const [first, second, third] = range(10, 50, 10);
console.log(first, second, third); // 10 20 30Infinite generators — safe because they're lazy
Generators produce values on demand — they don't calculate everything upfront. This makes infinite sequences safe (as long as you stop asking eventually):
// Infinite sequence — never ends, but that's fine:
function* naturals(start = 0) {
let n = start;
while (true) {
yield n++;
}
}
// Only take what you need:
function take(n, iterable) {
const result = [];
for (const val of iterable) {
result.push(val);
if (result.length >= n) break;
}
return result;
}
console.log(take(5, naturals())); // [0, 1, 2, 3, 4]
console.log(take(5, naturals(100))); // [100, 101, 102, 103, 104]Making your own iterable object
// Add [Symbol.iterator] to any object to make it iterable:
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const nums = new NumberRange(1, 5);
console.log([...nums]); // [1, 2, 3, 4, 5]
for (const n of nums) {
console.log(n); // 1, 2, 3, 4, 5
}
// Can iterate multiple times — each call to [Symbol.iterator]() creates a fresh iterator:
console.log([...nums]); // [1, 2, 3, 4, 5] — works againOfficial Sources
- MDN Web Docs — Iteration protocols
- MDN Web Docs — function*
- ECMAScript 2024 — Generator Objects (§27.5)
- MDN Web Docs — for...of
- TC39 — Iterator Helpers Proposal
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.