Iterators and Generators
The Iteration Protocol
JavaScript defines a standard iteration protocol that any object can implement. An object is iterable if it has a [Symbol.iterator] method that returns an iterator — an object with a next() method returning { value, done } pairs. Arrays, strings, Sets, and Maps are iterable by default.
// Custom iterable: a range of numbers
function range(start, end) {
return {
[Symbol.iterator]() {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
}
for (const n of range(1, 5)) {
console.log(n); // 1 2 3 4 5
}
console.log([...range(1, 5)]); // [1,2,3,4,5]Generator Functions
Generator functions (declared with function*) are a concise way to write iterators. Each yield expression pauses execution and produces a value. The function resumes from where it left off on the next call to next().
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const gen = range(1, 3);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Works with all iterable consumers
console.log([...range(1, 5)]); // [1,2,3,4,5]Infinite Sequences and Lazy Evaluation
Generators are lazy — they compute values on demand. This makes it practical to represent infinite sequences without running out of memory.
function* naturals() {
let n = 1;
while (true) yield n++; // infinite!
}
function take(n, iter) {
const result = [];
for (const v of iter) {
result.push(v);
if (result.length === n) break;
}
return result;
}
take(5, naturals()); // [1, 2, 3, 4, 5]Generators also power async generators (async function*) which yield Promises, enabling streaming data consumption with for await...of. This pattern is used in Node.js streams and the Web Streams API.