The Codex / JavaScript / Iterators & Generators

Iterators & Generators

The iteration protocol, custom iterables, generator functions, and lazy sequences in JavaScript.

What it is, in plain English: An iterator is any object with a next() method that returns {value, done} pairs. An iterable is any object that has a [Symbol.iterator]() method returning an iterator. Arrays, strings, Maps, Sets, and NodeLists are all iterables — that's why for...of works on them. A generator function (function*) is a special function that can pause mid-execution with yield, producing one value at a time. Generators are the easiest way to create custom iterables.

Iterators, iterables, and generator functions

You've used for...of loops to iterate over arrays and strings. But how does JavaScript know how to loop over them? The answer is the iteration protocol — a contract that any object can implement to become "loopable." Generator functions make implementing this contract easy.

The iteration protocol

An object is iterable if it has a [Symbol.iterator]() method that returns an iterator — an object with a next() method that returns { value, done } pairs.

// Arrays are built-in iterables:
const arr = [10, 20, 30];

// for...of uses the iterator protocol under the hood:
for (const n of arr) {
  console.log(n);   // 10, 20, 30
}

// You can call the protocol manually:
const iter = arr[Symbol.iterator]();
console.log(iter.next());  // { value: 10, done: false }
console.log(iter.next());  // { value: 20, done: false }
console.log(iter.next());  // { value: 30, done: false }
console.log(iter.next());  // { value: undefined, done: true }

Strings, Maps, Sets, NodeLists, and arguments objects are all built-in iterables. That's why for...of, spread (...), destructuring, and Array.from() all work on them.

Generator functions — the easy way to make iterables

Writing the iterator protocol by hand is verbose. Generator functions (function*) do it for you. A generator can yield a value and then pause — it resumes on the next .next() call.

// function* marks a generator function
function* countdown(from) {
  while (from > 0) {
    yield from;   // ← pause here, return 'from', wait for next .next()
    from--;
  }
  yield "Go!";
}

// A generator is automatically an iterable:
for (const value of countdown(3)) {
  console.log(value);  // 3, 2, 1, Go!
}

// Spread works too:
console.log([...countdown(3)]);  // [3, 2, 1, "Go!"]

A range generator — the most useful first generator

// Python has range(). JS doesn't — but you can write it:
function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) {
    yield i;
  }
}

for (const n of range(1, 5)) {
  console.log(n);  // 1, 2, 3, 4, 5
}

console.log([...range(0, 10, 2)]);  // [0, 2, 4, 6, 8, 10]

// Works in destructuring:
const [first, second, third] = range(10, 50, 10);
console.log(first, second, third);  // 10 20 30

Infinite generators — safe because they're lazy

Generators produce values on demand — they don't calculate everything upfront. This makes infinite sequences safe (as long as you stop asking eventually):

// Infinite sequence — never ends, but that's fine:
function* naturals(start = 0) {
  let n = start;
  while (true) {
    yield n++;
  }
}

// Only take what you need:
function take(n, iterable) {
  const result = [];
  for (const val of iterable) {
    result.push(val);
    if (result.length >= n) break;
  }
  return result;
}

console.log(take(5, naturals()));      // [0, 1, 2, 3, 4]
console.log(take(5, naturals(100)));   // [100, 101, 102, 103, 104]

Making your own iterable object

// Add [Symbol.iterator] to any object to make it iterable:
class NumberRange {
  constructor(start, end) {
    this.start = start;
    this.end   = end;
  }

  [Symbol.iterator]() {
    let current = this.start;
    const end   = this.end;
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

const nums = new NumberRange(1, 5);
console.log([...nums]);   // [1, 2, 3, 4, 5]

for (const n of nums) {
  console.log(n);  // 1, 2, 3, 4, 5
}

// Can iterate multiple times — each call to [Symbol.iterator]() creates a fresh iterator:
console.log([...nums]);   // [1, 2, 3, 4, 5] — works again
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.