Iterators and Generators in JavaScript
Iterators and Generators in JavaScript
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
We've used for...of on arrays since chapter 7 without asking WHY it works. This chapter answers that – and shows how to give our own budget app account type the same ability.
The iterator protocol
for...of works on ANY value that satisfies the "iterable" protocol: an object with a method under the special key Symbol.iterator, which returns an ITERATOR – an object with a next() method that returns { value, done } on each call.
const amounts = [100, 200, 300];
const iterator = amounts[Symbol.iterator]();
console.log(iterator.next()); // { value: 100, done: false }
console.log(iterator.next()); // { value: 200, done: false }
console.log(iterator.next()); // { value: 300, done: false }
console.log(iterator.next()); // { value: undefined, done: true }This is EXACTLY what happens internally on EVERY for...of iteration – the loop repeatedly calls next() until it gets back done: true. Arrays, strings, Map, and Set already implement this protocol out of the box.
Generator functions: building iterators easily
Writing an iterator by hand (as above) is tedious. Generator functions (recognizable by function*) handle the { value, done } bookkeeping automatically – yield "pauses" the function and emits a value, until next() is called again:
function* countMonths(from, to) {
for (let month = from; month <= to; month++) {
yield month;
}
}
for (const month of countMonths(1, 3)) {
console.log(month); // 1, then 2, then 3
}
// Also controllable manually, like a regular iterator:
const gen = countMonths(1, 3);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }Practical example: a custom iterable account collection
Let's equip a collection of accounts with Symbol.iterator – so for...of can be applied to it directly, instead of needing a separate getAllAccounts() method:
export class AccountCollection {
#accounts = [];
add(account) {
this.#accounts.push(account);
}
*[Symbol.iterator]() { // generator method shorthand inside a class
for (const account of this.#accounts) {
yield account;
}
}
}import { AccountCollection } from './accountCollection.js';
const collection = new AccountCollection();
collection.add(new Account('Checking', 1500));
collection.add(new SavingsAccount('Money Market', 5000, 2.5));
for (const account of collection) { // works THANKS TO Symbol.iterator, just like an array!
console.log(account.formatBalance());
}Tipp: The spread operator ([...collection]) and array destructuring also work automatically with ANY iterable object – another benefit of implementing the protocol instead of merely offering a getter method.
Generators for (theoretically) infinite sequences
One advantage of generators over arrays: values are only produced on demand ("lazy"), so even infinite sequences can be modeled without blowing up memory:
function* monthCycle() {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
let index = 0;
while (true) { // runs forever, but harmless thanks to 'lazy' evaluation
yield months[index % 12];
index++;
}
}
const cycle = monthCycle();
console.log(cycle.next().value); // 'Jan'
console.log(cycle.next().value); // 'Feb'
// ... would simply continue with 'Jan' again after 'Dec'