Iterators & Generators
Iterators define custom iteration behavior. Generators are special functions that can pause and resume. Together they power lazy evaluation and infinite sequences.
The Iterator Protocol
An iterator is an object with a next() method that returns { value, done }:
function createRangeIterator(start, end) {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
const iter = createRangeIterator(1, 3);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }
The Iterable Protocol
An iterable is an object with Symbol.iterator method that returns an iterator. This is what for...of uses:
class Range {
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 range = new Range(1, 5);
// Now works with for...of
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// And spread
console.log([...range]); // [1, 2, 3, 4, 5]
// And destructuring
const [a, b, c] = range;
Generator Functions
Generators are special functions that can pause and resume using yield. They automatically implement the iterator protocol:
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
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 }
// Or use for...of
for (const val of simpleGenerator()) {
console.log(val); // 1, 2, 3
}
The function* syntax and yield keyword are what make it a generator. Execution pauses at each yield and resumes when next() is called.
Infinite Sequences
Generators are great for lazy, infinite sequences:
function* naturals() {
let n = 1;
while (true) {
yield n++;
}
}
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// Take only what you need
function take(iterable, n) {
const result = [];
for (const val of iterable) {
result.push(val);
if (result.length === n) break;
}
return result;
}
console.log(take(naturals(), 5)); // [1, 2, 3, 4, 5]
console.log(take(fibonacci(), 8)); // [0, 1, 1, 2, 3, 5, 8, 13]
Passing Values into Generators
next(value) sends a value back into the generator, replacing the yield expression:
function* accumulator() {
let total = 0;
while (true) {
const value = yield total; // pauses here, yields total
total += value; // resumes here with the sent value
}
}
const acc = accumulator();
acc.next(); // start (first call doesn't send a value)
console.log(acc.next(5).value); // 5
console.log(acc.next(10).value); // 15
console.log(acc.next(3).value); // 18
yield* โ Delegate to Another Iterable
function* concat(...iterables) {
for (const iterable of iterables) {
yield* iterable; // delegate to sub-iterable
}
}
console.log([...concat([1, 2], [3, 4], [5])]); // [1, 2, 3, 4, 5]
// Flatten one level
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) yield* item;
else yield item;
}
}
console.log([...flatten([1, [2, 3], 4, [5, 6]])]); // [1, 2, 3, 4, 5, 6]
Async Generators
Combine generators with async/await for async iteration:
async function* paginate(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.items.length === 0) break;
yield data.items;
page++;
}
}
// Use with for await...of
async function loadAll() {
for await (const items of paginate("/api/posts")) {
console.log("Page of items:", items);
// process items...
}
}
Practical Uses
Unique ID generator
function* idGenerator(prefix = "id") {
let n = 1;
while (true) {
yield `${prefix}_${n++}`;
}
}
const ids = idGenerator("user");
console.log(ids.next().value); // "user_1"
console.log(ids.next().value); // "user_2"
console.log(ids.next().value); // "user_3"
State machine
function* trafficLight() {
while (true) {
yield "green";
yield "yellow";
yield "red";
}
}
const light = trafficLight();
for (let i = 0; i < 6; i++) {
console.log(light.next().value);
}
// green, yellow, red, green, yellow, red
Key Takeaways
- The iterator protocol defines
next()returning{ value, done } - The iterable protocol defines
[Symbol.iterator]()returning an iterator - Generators (
function*) pause atyieldand resume onnext() - Generators are great for lazy evaluation and infinite sequences
yield*delegates to another iterable- Async generators enable async iteration over paginated data
Ready to test your knowledge?
Take a quiz on what you just learned.