JavaScript Iterator & Generator Reference
Iterator protocol, generator functions, and async iterators — with syntax, parameters, edge cases, and a live code runner
| API | Purpose | ES |
|---|---|---|
| Symbol.iterator | Defines the default iterator for an object. | ES2015 |
| for...of | Loops over any iterable object, calling [Symbol. | ES2015 |
| next() | Advances the iterator by one step and returns { value, done }. | ES2015 |
| return() | Terminates the iterator early and returns { value, done: true }. | ES2015 |
| throw() | Injects an exception into the generator at the paused yield point. | ES2015 |
| Array.from() | Creates a new array from any iterable or array-like object, exhausting the iterator completely. | ES2015 |
| spread (...) | Expands an iterable's values into an array literal or function arguments. | ES2015 |
Symbol.iteratorIterator ProtocolES2015obj[Symbol.iterator]() { return iteratorObject; }Defines the default iterator for an object. Any object that implements a [Symbol.iterator]() method returning an iterator is called an *iterable* — it can be used in for...of loops, spread expressions, destructuring assignments, and Array.from().
for...ofIterator ProtocolES2015for (const item of iterable) { ... }Loops over any iterable object, calling [Symbol.iterator]() once and then next() on each iteration until done is true. Works with arrays, strings, Maps, Sets, generators, and any object implementing the iterable protocol.
next()Iterator ProtocolES2015iterator.next(value?)Advances the iterator by one step and returns { value, done }. The optional value argument becomes the result of the yield expression inside a generator — enabling two-way communication. When done is true, the iteration is complete.
return()Iterator ProtocolES2015iterator.return(value?)Terminates the iterator early and returns { value, done: true }. Inside generators it executes any pending finally blocks, allowing resource cleanup. Called automatically by for...of when the loop exits via break or return.
throw()Iterator ProtocolES2015iterator.throw(error)Injects an exception into the generator at the paused yield point. If the generator has a try...catch around the paused yield, it can handle the error and continue. If not caught, the exception propagates to the throw() caller and the generator terminates.
Array.from()Iterator ProtocolES2015Array.from(iterable, mapFn?)Creates a new array from any iterable or array-like object, exhausting the iterator completely. Accepts an optional mapping function applied to each element during conversion.
spread (...)Iterator ProtocolES2015[...iterable] / fn(...iterable)Expands an iterable's values into an array literal or function arguments. Internally calls [Symbol.iterator]() and exhausts the entire iterator. Can be combined with other values in array literals.
You’re paginating API results — each page is a fetch call, and you want to process items one at a time across pages without loading all 10,000 results into memory. You could write a recursive function with callbacks, or you could use an async generator: async function* fetchPages(url) that yields items one by one and for await...of consumes them. But the iterator protocol, Symbol.iterator vs Symbol.asyncIterator, yield vs yield*, and next()/return()/throw() methods are a lot to hold in your head.
Why This Reference (Not MDN)
MDN splits iterators, generators, async iterators, and async generators across 6+ pages. This reference puts the entire protocol on one page — Symbol.iterator, for...of, next()/return()/throw(), function*, yield, yield*, Symbol.asyncIterator, async function*, and for await...of — with interactive examples.
What Are JavaScript Iterators and Generators?
The iterator protocol and generator functions are JavaScript’s built-in mechanism for lazy, on-demand sequences. Instead of computing all values upfront and storing them in an array, iterators produce values one at a time — only when requested.
- Iterator: any object with a
next()method returning{ value, done } - Iterable: any object with a
[Symbol.iterator]()method returning an iterator - Generator: a function written with
function*that produces an iterator automatically — with built-in pause/resume semantics viayield - Async Iterator / Generator: the async counterparts using
Symbol.asyncIterator,async function*, andfor await...of
// The simplest custom iterator
const counter = {
[Symbol.iterator]() {
let n = 0;
return {
next() {
return n < 3
? { value: n++, done: false }
: { value: undefined, done: true };
}
};
}
};
for (const n of counter) console.log(n); // 0 1 2
console.log([...counter]); // [0, 1, 2]
The Iterator Protocol
Symbol.iterator — Making Objects Iterable
Any object becomes iterable by implementing [Symbol.iterator](). This method is called once when the iterable enters a for...of loop or a spread expression, and must return an iterator.
class Range {
constructor(from, to) { this.from = from; this.to = to; }
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
}
const r = new Range(1, 5);
console.log([...r]); // [1, 2, 3, 4, 5]
console.log(Array.from(r)); // [1, 2, 3, 4, 5]
const [first, second] = r;
console.log(first, second); // 1 2
for…of — Iterating Over Any Iterable
for...of works with all built-in iterables (Array, String, Map, Set, TypedArray, arguments, NodeList, generators) and any custom object implementing [Symbol.iterator]().
// Arrays
for (const n of [10, 20, 30]) console.log(n); // 10 20 30
// Strings (iterates Unicode code points, not bytes)
for (const ch of "hi 👋") console.log(ch); // h i (space) 👋
// Maps (key-value pairs)
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, val] of map) console.log(key, "->", val); // a -> 1 b -> 2
// Sets (unique values)
for (const v of new Set([1, 2, 2, 3])) console.log(v); // 1 2 3
next(), return(), throw() — The Iterator Interface
Every iterator exposes three methods. next() is required; return() and throw() are optional but standard:
| Method | Purpose |
|---|---|
next(value?) | Advance one step; returns { value, done } |
return(value?) | Terminate early; triggers finally in generators |
throw(error) | Inject an exception at the paused yield |
function* source() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log("iterator closed");
}
}
const it = source();
console.log(it.next()); // { value: 1, done: false }
console.log(it.return("done")); // iterator closed { value: "done", done: true }
Generator Functions
function* — Declaring a Generator
Generator functions use function* syntax. Calling them does NOT execute the body — it returns a Generator object. The body runs only when next() is called:
function* countdown(n) {
while (n > 0) {
yield n--;
}
return "blastoff!";
}
const gen = countdown(3);
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: "blastoff!", done: true }
console.log(gen.next()); // { value: undefined, done: true }
The generator’s return value appears in the last { done: true } result — but for...of and spread skip it. To capture it you must call next() manually.
yield — Two-Way Communication
yield both produces a value outward and receives a value inward. The value passed to next(value) becomes the result of the paused yield expression:
function* logger() {
let count = 0;
while (true) {
const msg = yield count++;
if (msg === "reset") count = 0;
console.log("logged:", msg, "count now:", count);
}
}
const log = logger();
log.next(); // Start the generator
log.next("a"); // logged: a count now: 1
log.next("b"); // logged: b count now: 2
log.next("reset"); // logged: reset count now: 0
yield* — Delegating to Another Iterable
yield* flattens another iterable into the current generator’s output. It also collects the inner generator’s return value:
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) yield* flatten(item); // recursive
else yield item;
}
}
console.log([...flatten([1, [2, [3, 4]], 5])]); // [1, 2, 3, 4, 5]
Common Generator Patterns
Infinite Sequence
function* naturals(start = 1) {
while (true) yield start++;
}
function take(n, iter) {
const result = [];
for (const v of iter) {
result.push(v);
if (result.length === n) break;
}
return result;
}
console.log(take(5, naturals())); // [1, 2, 3, 4, 5]
Lazy Pipeline
function* map(iter, fn) {
for (const v of iter) yield fn(v);
}
function* filter(iter, pred) {
for (const v of iter) if (pred(v)) yield v;
}
function* take(n, iter) {
for (const v of iter) {
yield v;
if (--n === 0) break;
}
}
function* naturals() { let n = 0; while (true) yield n++; }
// Find the first 3 even squares — all lazily evaluated
const result = take(3, filter(map(naturals(), n => n * n), n => n % 2 === 0));
console.log([...result]); // [0, 4, 16]
State Machine
function* trafficLight() {
while (true) {
yield "green";
yield "yellow";
yield "red";
}
}
const light = trafficLight();
console.log(light.next().value); // green
console.log(light.next().value); // yellow
console.log(light.next().value); // red
console.log(light.next().value); // green
Async Iterators and Generators
Symbol.asyncIterator — Async Iterable Protocol
Objects implement [Symbol.asyncIterator]() to become async iterables. The method returns an async iterator whose next() returns a Promise<{ value, done }>:
async function run() {
const timedSequence = {
[Symbol.asyncIterator]() {
const items = [1, 2, 3];
let i = 0;
return {
async next() {
await new Promise(r => setTimeout(r, 50));
return i < items.length
? { value: items[i++], done: false }
: { value: undefined, done: true };
}
};
}
};
for await (const n of timedSequence) {
console.log(n); // 1 2 3 (each with 50ms delay)
}
}
run();
async function* — Async Generator Functions
Async generators combine await and yield. They are ideal for paginated APIs, streaming responses, and any data that arrives asynchronously:
async function* fetchPages(url, maxPages) {
let page = 1;
while (page <= maxPages) {
// In real code: const data = await fetch(`${url}?page=${page}`).then(r => r.json());
await new Promise(r => setTimeout(r, 0)); // simulate network
yield { page, data: ["item" + (page * 2 - 1), "item" + (page * 2)] };
page++;
}
}
async function run() {
for await (const { page, data } of fetchPages("/api/items", 3)) {
console.log("page", page, "->", data.join(", "));
}
}
run();
for await…of — Async Iteration Loop
for await...of iterates async iterables, awaiting each item. It also works on sync iterables (wrapping each value with Promise.resolve):
async function* countdown(n) {
while (n > 0) {
await new Promise(r => setTimeout(r, 0));
yield n--;
}
}
async function run() {
for await (const n of countdown(3)) {
console.log(n); // 3 2 1
}
}
run();
Built-In Iterables Cheat Sheet
| Value | Symbol.iterator? | Yields |
|---|---|---|
Array | Yes | Elements |
String | Yes | Unicode code points (not UTF-16 code units) |
Map | Yes | [key, value] pairs |
Set | Yes | Values (in insertion order) |
TypedArray | Yes | Elements |
arguments | Yes | Argument values |
NodeList | Yes | DOM nodes |
| Generator | Yes | Yielded values (the generator IS its own iterator) |
Plain {} | No | — (use Object.entries / Object.keys) |
Frequently Asked Questions
What is the difference between an iterator and an iterable?
An iterator is an object with a next() method returning { value, done }. An iterable is an object with a [Symbol.iterator]() method that returns an iterator. These are separate protocols. Generators satisfy both: the generator object IS its own iterator (gen[Symbol.iterator]() === gen), so it is both an iterable and an iterator.
Can a generator be paused from outside?
No. Generators are cooperative — they pause themselves with yield. The caller cannot forcibly suspend a generator mid-execution. The only way to advance or interact with a generator is through its next(), return(), and throw() methods.
What happens to a generator after it returns?
After a generator’s function body completes (via return or falling off the end), the generator is exhausted. All subsequent next() calls return { value: undefined, done: true }. Calling return() on an exhausted generator is a no-op.
Why does for…of skip the generator’s return value?
The for...of loop stops when it sees { done: true } — and the value property of that last result is ignored. This is by design: the convention is that return values from generators are metadata, not part of the produced sequence. To capture a return value, call next() manually until done is true and read result.value.
How do I convert an async iterable to an array?
Array.from() and spread ([...]) do NOT work with async iterables — they only support sync iterables. To collect all values from an async iterable, use for await...of:
async function toArray(asyncIterable) {
const result = [];
for await (const item of asyncIterable) result.push(item);
return result;
}
When should I use a generator vs a regular function returning an array?
Use a generator when: (1) the sequence is lazy — you want to avoid computing all values upfront; (2) the sequence is infinite; (3) you need early termination and want cleanup via finally; (4) you are building a pipeline of transformations. Use an array when the full result is needed immediately, the sequence is small and finite, or you need random access.