Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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:

src/accountCollection.js
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'