Advanced30 min read

Iterators & Generators

Understand the iterator protocol and create custom iterables with generator functions using function* and yield.

The Iterator Protocol

An iterator is any object that implements a next() method. Each call to next() returns an object with two properties:

  • value — the next value in the sequence.
  • donetrue if the sequence is finished, false otherwise.
javascript
// A simple manual iterator
const iterator = {
  current: 1,
  last: 3,
  next() {
    if (this.current <= this.last) {
      return { value: this.current++, done: false };
    }
    return { value: undefined, done: true };
  }
};

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

Many built-in JavaScript types are iterable: arrays, strings, Maps, Sets, NodeLists, and more. The for...of loop, spread operator (...), and destructuring all use the iterator protocol under the hood.

javascript
for (const char of 'hello') {
  console.log(char); // 'h', 'e', 'l', 'l', 'o'
}

The Iterable Protocol

An object is iterable if it has a method at the key Symbol.iterator that returns an iterator.

javascript
const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

Now you can use this custom iterable with any iteration feature:

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

// Spread operator
const nums = [...range]; // [1, 2, 3, 4, 5]

// Destructuring
const [a, b, c] = range; // a=1, b=2, c=3

// Array.from
const arr = Array.from(range); // [1, 2, 3, 4, 5]

The iterable protocol is what makes JavaScript's iteration features so flexible and consistent. Any object that implements [Symbol.iterator]() can participate in the iteration ecosystem.

Generator Functions

Writing iterators manually is verbose. Generator functions provide a much simpler way to create iterators.

A generator function is declared with function* (note the asterisk). Inside, you use the yield keyword to produce values.

javascript
function* countUp(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

const gen = countUp(1, 3);
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 }

Key features:

  • yield pauses the function and produces a value.
  • Calling next() resumes from where it paused.
  • Generators are lazy — values are computed on demand, not all at once.
  • Generators are iterable — they implement both protocols automatically.
javascript
for (const n of countUp(1, 5)) {
  console.log(n); // 1, 2, 3, 4, 5
}

const nums = [...countUp(10, 15)]; // [10, 11, 12, 13, 14, 15]

yield* delegates to another iterable:

javascript
function* combined() {
  yield* [1, 2, 3];
  yield* 'abc';
}
// Produces: 1, 2, 3, 'a', 'b', 'c'

Use Cases

Generators are particularly powerful for scenarios that benefit from lazy evaluation — producing values only when needed.

Infinite sequences — generate values forever, consuming them one at a time:

javascript
function* ids() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const idGen = ids();
console.log(idGen.next().value); // 1
console.log(idGen.next().value); // 2
console.log(idGen.next().value); // 3
// Can continue forever

Fibonacci sequence:

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

Lazy processing of large datasets — avoid loading everything into memory:

javascript
function* filterLarge(items, predicate) {
  for (const item of items) {
    if (predicate(item)) yield item;
  }
}

Pagination:

javascript
function* paginate(items, pageSize) {
  for (let i = 0; i < items.length; i += pageSize) {
    yield items.slice(i, i + pageSize);
  }
}

Async generators combine generators with async/await for streaming async data:

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

Generator in Action

html
<div id="output"></div>

<script>
  // Fibonacci generator
  function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
      yield a;
      [a, b] = [b, a + b];
    }
  }

  // Take the first N values from a generator
  function take(gen, n) {
    const result = [];
    for (const value of gen) {
      result.push(value);
      if (result.length >= n) break;
    }
    return result;
  }

  const first10 = take(fibonacci(), 10);

  // Range generator
  function* range(start, end, step = 1) {
    for (let i = start; i <= end; i += step) {
      yield i;
    }
  }

  const evens = [...range(0, 20, 2)];

  const output = document.getElementById('output');
  output.innerHTML = `
    <p>First 10 Fibonacci: [${first10.join(', ')}]</p>
    <p>Even numbers 0-20: [${evens.join(', ')}]</p>
  `;
</script>

What does the yield keyword do in a generator?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.