JavaScript Iterators
Learn iterators, the iteration protocol, and generators for controlling how data is consumed.
TL;DR
- 01Iterators expose a
nextmethod returningvalueanddone. - 02Add
Symbol.iteratorto make any custom object iterable. - 03Use generator functions to build iterators with less code.
Tips
- 01Reach for generators before writing manual iterators, since <code>function*</code> and <code>yield</code> handle state and the <code>next()</code> contract for you automatically.
- 02Use yield* to delegate to another iterable inside a generator, which avoids manually looping and re-yielding each value.
Warnings
- 01Iterators are one-time use — once <code>done</code> is <code>true</code>, create a new iterator to traverse the data again.
- 02Plain objects are not iterable by default, so for-of on one directly throws a TypeError unless you call Object.entries() first.
What Iterators Are
- Every for-of loop, spread, and destructuring call secretly relies on one tiny contract: a
next()method.const arr = [10, 20]; const it = arr[Symbol.iterator](); it.next(); // { value: 10, done: false } - Each call to
next()returns an object withvalueanddoneproperties. - Built-in features like
for...of, spread, and destructuring use iterators behind the scenes. - Iterators let you control how a collection is traversed, one item at a time.
- When
donebecomestrue, the iteration is finished andvalueis undefined.
Iteration Protocol
- Iterable and iterator are different roles — an iterable's job is just to hand back an iterator on request.
const obj = { data: [1, 2, 3], [Symbol.iterator]() { let i = 0; return { next: () => ({ value: this.data[i], done: i++ >= this.data.length }) }; } }; for (const n of obj) console.log(n); - This protocol lets you use your own objects in
for...ofloops. - The returned iterator must follow the same
next()shape as built-in ones. - Custom iterables work everywhere standard iterables do, like spread syntax.
- Most built-in objects already follow this protocol out of the box.
Built-in Iterables
- Plain objects are the one common exception — arrays, strings, Maps, and Sets all ship iterable, objects don't.
for (const char of "Hi!") console.log(char); - Use
for...ofto loop over any built-in iterable cleanly.const set = new Set([1, 2]); for (const v of set) console.log(v); - Maps yield
[key, value]pairs, which you can destructure in the loop.const map = new Map([['a', 1]]); for (const [k, v] of map) console.log(k, v); - NodeLists from the DOM are also iterable using
for...of. - Plain objects are not iterable by default — use
Object.entries()first.
Spread and Custom Iterators
- Spread syntax has no idea what a Set or Map is internally — it just calls Symbol.iterator like everything else.
const unique = new Set([1, 2, 2]); console.log([...unique]); // [1, 2] - Build a manual iterator with full control over the sequence and end condition.
function makeCounter(limit) { let count = 0; return { next() { if (count < limit) return { value: count++, done: false }; return { done: true }; } }; } - Custom iterators are useful for generating infinite or computed sequences.
- The spread operator is also great for cloning arrays and merging iterables.
- Iterators stop being useful once exhausted, so create a new one if needed.
Generators
- A generator can pause mid-function and resume later with a new value injected — no other JavaScript construct does that.
function* greet() { const name = yield "What's your name?"; yield `Hello, ${name}!`; } const g = greet(); g.next().value; // "What's your name?" g.next("Ada").value; // "Hello, Ada!" - The
yieldkeyword pauses execution and returns the next value. - Pass data back into the generator using
next(value)for two-way communication. - Delegate to another iterable using
yield*inside a generator function.function* combine() { yield* [1, 2]; yield* ['a', 'b']; } console.log([...combine()]); - Generators are the easiest way to create custom iterators in modern JavaScript.
FAQ
An iterable is any object with a Symbol.iterator method that returns an iterator. An iterator is the object with the next() method that actually produces values. Arrays are iterables, but array.values() returns the iterator.
Yes — add a Symbol.iterator method to your object that returns an object with a next() method returning {value, done}. Once that's in place, for...of, destructuring, and spread will all work on it.
Iterators maintain internal state and are exhausted after reaching done: true. Calling next() again just keeps returning {value: undefined, done: true}. To iterate again, call Symbol.iterator() to get a fresh iterator instance.
Use a generator (function*) when your iterator needs to track state between yields, since the runtime manages context for you. Hand-written iterators are only worth the extra code when you need fine-grained control over the object itself.
Yes — spread ([...obj]) and destructuring both rely on Symbol.iterator under the hood. Any object implementing the iteration protocol works with them. This includes built-ins like Map, Set, and String, as well as your own custom iterables.