Explorer
JavaScript

Iterators and Generators

JavaScript Theory & Concepts

Iterators and Generators in JavaScript

Custom iteration protocols, lazy evaluation, and pausing function execution with generators.

📖 The Story & Real-World Analogy

The Book Bookmark and the Automated Pez Dispenser

"Imagine reading an encyclopedic book with millions of pages. An Iterator is a mechanical bookmark with a thumb button: every time you press it (`.next()`), it flips forward exactly one page and reports `{ value: pageContent, done: false }`. When you reach the back cover, it reports `{ done: true }`. A Generator (`function*`) is an automated Pez dispenser: when you pull the lever, it yields a fresh piece of candy and pauses time until you pull the lever again. It can generate infinite candy without needing a factory warehouse to store all infinite candies at once (lazy evaluation)!"

Iterators and Generators standardize how collections are traversed and enable lazy data streams that produce values on demand rather than allocating memory up front.

⚙️ How It Works Under The Hood (Step-by-Step)
1

The Iterable Protocol

An object is iterable if it implements the `[Symbol.iterator]` method, which returns an iterator object (used by `for...of`, spread `[...]`, and `Array.from()`).

2

The Iterator Protocol

An object is an iterator if it implements a `.next()` method returning `{ value: any, done: boolean }`.

3

Generators (function* and yield)

Generators are special functions denoted with `function*`. Calling a generator returns a Generator object without running its body. Calling `.next()` executes code until hitting the next `yield` keyword, where execution freezes.

4

Two-Way Message Passing

Passing an argument into `generator.next(val)` replaces the result of the paused `yield` expression inside the generator body, allowing bi-directional communication.

💻 Interactive Code Walkthrough

Creating a custom Fibonacci generator producing values lazily on demand:

JAVASCRIPT
// Infinite lazy Fibonacci generator
function* fibonacciGenerator() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacciGenerator();

// Pulling values on demand without memory explosion
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5

// Consuming first 4 numbers with a custom helper
function take(iterable, count) {
  const result = [];
  for (const val of iterable) {
    result.push(val);
    if (result.length >= count) break;
  }
  return result;
}

console.log("Next 4 Fibonacci:", take(fib, 4));
Console Output:
CODE
1
1
2
3
5
Next 4 Fibonacci: [ 8, 13, 21, 34 ]
⚠️ Common Pitfalls & Interview Traps
Trap
Exhausted Generators Cannot Be Rewound

The Risk: Once a generator yields `{ done: true }`, calling `.next()` repeatedly continues returning `{ value: undefined, done: true }`.

The Fix: To iterate again, invoke the generator function to instantiate a fresh generator instance.

⚡ 30-Second Quick Revision Cheat Sheet (TL;DR)
  • ✓ Iterables implement `[Symbol.iterator]()`.
  • ✓ Iterators implement `.next() -> { value, done }`.
  • ✓ Generators use `function*` syntax and pause at `yield`.
  • ✓ Generators enable memory-efficient lazy computation of large or infinite streams.
  • ✓ Passing values to `.next(arg)` sends data into the generator.

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.