Skip to main content
โœจintermediate

Iterators & Generators

Iterators define custom iteration behavior. Generators are special functions that can pause and resume. Together they power lazy evaluation and infinite sequences.

The Iterator Protocol

An iterator is an object with a next() method that returns { value, done }:

hljs javascript
function createRangeIterator(start, end) {
  let current = start;
  return {
    next() {
      if (current <= end) {
        return { value: current++, done: false };
      }
      return { value: undefined, done: true };
    }
  };
}

const iter = createRangeIterator(1, 3);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }

The Iterable Protocol

An iterable is an object with Symbol.iterator method that returns an iterator. This is what for...of uses:

hljs javascript
class Range {
  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 range = new Range(1, 5);

// Now works with for...of
for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

// And spread
console.log([...range]); // [1, 2, 3, 4, 5]

// And destructuring
const [a, b, c] = range;

Generator Functions

Generators are special functions that can pause and resume using yield. They automatically implement the iterator protocol:

hljs javascript
function* simpleGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = simpleGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Or use for...of
for (const val of simpleGenerator()) {
  console.log(val); // 1, 2, 3
}

The function* syntax and yield keyword are what make it a generator. Execution pauses at each yield and resumes when next() is called.

Infinite Sequences

Generators are great for lazy, infinite sequences:

hljs javascript
function* naturals() {
  let n = 1;
  while (true) {
    yield n++;
  }
}

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

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

console.log(take(naturals(), 5));   // [1, 2, 3, 4, 5]
console.log(take(fibonacci(), 8));  // [0, 1, 1, 2, 3, 5, 8, 13]

Passing Values into Generators

next(value) sends a value back into the generator, replacing the yield expression:

hljs javascript
function* accumulator() {
  let total = 0;
  while (true) {
    const value = yield total; // pauses here, yields total
    total += value;            // resumes here with the sent value
  }
}

const acc = accumulator();
acc.next();     // start (first call doesn't send a value)
console.log(acc.next(5).value);  // 5
console.log(acc.next(10).value); // 15
console.log(acc.next(3).value);  // 18

yield* โ€” Delegate to Another Iterable

hljs javascript
function* concat(...iterables) {
  for (const iterable of iterables) {
    yield* iterable; // delegate to sub-iterable
  }
}

console.log([...concat([1, 2], [3, 4], [5])]); // [1, 2, 3, 4, 5]

// Flatten one level
function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) yield* item;
    else yield item;
  }
}

console.log([...flatten([1, [2, 3], 4, [5, 6]])]); // [1, 2, 3, 4, 5, 6]

Async Generators

Combine generators with async/await for async iteration:

hljs javascript
async function* paginate(url) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    if (data.items.length === 0) break;
    yield data.items;
    page++;
  }
}

// Use with for await...of
async function loadAll() {
  for await (const items of paginate("/api/posts")) {
    console.log("Page of items:", items);
    // process items...
  }
}

Practical Uses

Unique ID generator

hljs javascript
function* idGenerator(prefix = "id") {
  let n = 1;
  while (true) {
    yield `${prefix}_${n++}`;
  }
}

const ids = idGenerator("user");
console.log(ids.next().value); // "user_1"
console.log(ids.next().value); // "user_2"
console.log(ids.next().value); // "user_3"

State machine

hljs javascript
function* trafficLight() {
  while (true) {
    yield "green";
    yield "yellow";
    yield "red";
  }
}

const light = trafficLight();
for (let i = 0; i < 6; i++) {
  console.log(light.next().value);
}
// green, yellow, red, green, yellow, red
โ–ถTry it yourself

Key Takeaways

  • The iterator protocol defines next() returning { value, done }
  • The iterable protocol defines [Symbol.iterator]() returning an iterator
  • Generators (function*) pause at yield and resume on next()
  • Generators are great for lazy evaluation and infinite sequences
  • yield* delegates to another iterable
  • Async generators enable async iteration over paginated data

Ready to test your knowledge?

Take a quiz on what you just learned.

Take the Quiz โ†’