Q: What are generators and iterators in JavaScript? When are they useful?
Answer:
An iterator is any object with a next() method that returns { value, done }. An iterable is any object with a [Symbol.iterator]() method that returns an iterator. for...of, spread, destructuring, and Array.from all operate on iterables.
A generator function (function*) is the easiest way to build both — it returns an object that is both an iterator and an iterable, and it can yield values lazily.
Basic Generator
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) yield i;
}
for (const n of range(0, 5)) console.log(n); // 0 1 2 3 4
[...range(0, 3)]; // [0, 1, 2]
Execution pauses at each yield and resumes on the next .next() call — no buffering of the full sequence.
The Iterator Protocol — by Hand
const counter = {
[Symbol.iterator]() {
let i = 0;
return {
next() { return i < 3 ? { value: i++, done: false } : { value: undefined, done: true }; }
};
}
};
for (const n of counter) console.log(n); // 0 1 2
Why Generators Matter
1. Infinite/lazy sequences. You can describe streams that would never fit in memory.
function* naturals() { let n = 1; while (true) yield n++; }
function* take(iter, k) { for (const v of iter) { if (k-- <= 0) return; yield v; } }
[...take(naturals(), 5)]; // [1,2,3,4,5]
2. Pipeline composition. Each step yields one item at a time:
function* map(iter, f) { for (const v of iter) yield f(v); }
function* filter(iter, f) { for (const v of iter) if (f(v)) yield v; }
[...take(filter(map(naturals(), x => x * x), x => x % 2), 5)];
// [1, 9, 25, 49, 81]
3. Two-way communication via .next(value). A consumer can push data back into a paused generator. This is the foundation of older coroutine-style async (co, redux-saga).
function* dialog() {
const name = yield 'What is your name?';
yield `Hello, ${name}`;
}
const g = dialog();
g.next(); // { value: 'What is your name?', done: false }
g.next('Ada'); // { value: 'Hello, Ada', done: false }
g.next(); // { value: undefined, done: true }
Async Iterators (Symbol.asyncIterator)
Async generators (async function*) yield Promises and are consumed with for await...of:
async function* lines(stream) {
let buf = '';
for await (const chunk of stream) {
buf += chunk;
let i; while ((i = buf.indexOf('\n')) >= 0) {
yield buf.slice(0, i);
buf = buf.slice(i + 1);
}
}
if (buf) yield buf;
}
for await (const line of lines(fileStream)) processLine(line);
Streaming pagination, server-sent events, and reading large files become elegant one-shot loops.
Generators vs Async/Await
async/await = generator + auto-runner that awaits each yielded promise
generators = manual control flow primitive
Most application code should reach for async/await. Reach for generators when you need:
- Pull-based lazy streams.
- Custom iteration protocols.
- Pausable / cancellable coroutines.
[!NOTE] Generators are not parallel. They cooperatively pause; the engine is still single-threaded.
Termination
return(value)ends the generator early as ifreturn value;ran in place of the nextyield.throw(err)injects an error at the suspendedyield, allowing atry/catchinside the generator to handle it.
Real-World Sightings
- Node streams expose async iterators (
for await (const chunk of req)). - Redux-Saga uses synchronous generators for testable side-effect orchestration.
- Web Streams'
ReadableStreamworks withfor await.