Iterators and Generators
- An iterator is an object with a
next()method that returns{ value, done }. Each call gives the next value untildoneistrue. - An iterable is an object with a
[Symbol.iterator]()method that returns an iterator. Arrays, strings,Map,Set,argumentsand DOM collections are iterable; plain objects are not. - A generator is a function declared with
function*. Calling it does not run the body: it returns a generator object, which is both an iterator and an iterable. Eachnext()runs the body up to the nextyieldand pauses there.
for...of, spread, destructuring, Array.from, new Map(), new Set() and
Promise.all all accept any iterable.
Details
The Protocols
const iterator = [10, 20][Symbol.iterator]();
iterator.next(); // { value: 10, done: false }
iterator.next(); // { value: 20, done: false }
iterator.next(); // { value: undefined, done: true }
- An iterator is one pass: once it is done, it stays done. An iterable can usually create a new iterator each time.
- Iteration is lazy: values are produced only when asked for. That makes infinite sequences possible.
- An iterator may have a
return()method.for...ofcalls it when the loop stops early (break,return,throw), so the source can clean up.
Generators
| Syntax | What it does |
|---|---|
function* gen() {} | declares a generator function; also *method() {} in a class |
yield value | pauses and gives value to the caller |
yield* iterable | yields every value of another iterable or generator |
return value | finishes: { value, done: true }; for...of ignores this value |
gen.next(x) | resumes; x becomes the result of the paused yield expression |
gen.return(x) | finishes the generator; finally blocks still run |
gen.throw(error) | throws the error at the paused yield |
Generators are the shortest way to write an iterator: the state (where to continue) is kept by the engine instead of being tracked by hand.
Iterator Helpers (ES2025)
Iterators have their own lazy methods, like the array ones: map, filter,
take, drop, flatMap, reduce, toArray, forEach, some, every,
find. Iterator.from(obj) wraps any iterator or iterable.
- Unlike array methods, they don't create intermediate arrays and work with infinite sequences.
- They are methods of iterators, not of iterables: call
map.values().map(…), notmap.map(…). - Baseline 2025: supported in current browsers and Node.js 22+.
Async Iteration
- An async iterator's
next()returns a promise of{ value, done }. An async iterable has[Symbol.asyncIterator](). for await...ofconsumes it, inside an async function or a module. It also accepts sync iterables of promises.async function*combines both: it canawaitandyield.- Typical sources: readable streams (
ReadableStreamin browsers, Node.js streams), paginated APIs, events. Array.fromAsync(asyncIterable)collects everything into an array (ES2026, Baseline 2024).
Example
Examples
A custom iterable written by hand and with a generator:
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next: () =>
current <= last
? { value: current++, done: false }
: { value: undefined, done: true },
};
},
};
[...range]; // [1, 2, 3]
const range2 = {
from: 1,
to: 3,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) yield i;
},
};
[...range2]; // [1, 2, 3]
An infinite sequence with iterator helpers:
function* naturals() {
let n = 1;
while (true) yield n++;
}
naturals()
.filter((n) => n % 2 === 0)
.map((n) => n * n)
.take(3)
.toArray(); // [4, 16, 36]
next(x) and cleanup:
function* dialog() {
try {
const name = yield 'What is your name?';
yield `Hello, ${name}!`;
} finally {
console.log('closed');
}
}
const talk = dialog();
talk.next().value; // 'What is your name?'
talk.next('Ann').value; // 'Hello, Ann!'
talk.return(); // logs 'closed'
Paginated API as an async generator:
async function* fetchPages(url) {
while (url) {
const response = await fetch(url);
const { items, next } = await response.json();
yield items;
url = next;
}
}
for await (const items of fetchPages('/api/users?page=1')) {
render(items);
}
Follow-up Questions
- How do you make a plain object iterable? Add
[Symbol.iterator], or iterate overObject.entries(obj). - What happens to a generator when
for...ofbreaks? The loop callsreturn(), the generator finishes and itsfinallyblocks run. - Why are generators described as "pausable functions"? The function's execution
context is saved at
yieldand restored onnext(). Beforeasync/await, libraries such ascoandredux-sagabuilt async flows on top of this. - Can you iterate over an iterator twice? No, it is exhausted. Get a new one from the iterable.
Common Mistakes
- Expecting a call to a generator function to run its body. Nothing runs until
the first
next(). - "
for...initerates values." It iterates property names of objects; usefor...offor values of iterables. - Spreading an infinite generator:
[...naturals()]never finishes. - Reusing an exhausted iterator, for example a generator object stored in a variable and passed to two loops: the second loop sees nothing.
- Calling iterator helpers on an iterable:
new Set([1]).mapisundefined; useset.values().map(…).