DEV Community

Mohsen Fallahnejad
Mohsen Fallahnejad

Posted on Edited on Originally published at bitlyst.blog

JavaScript Generators & Iterators — Lazy Sequences Explained

🧠 What's the Problem?

When you build an array of 1,000,000 numbers, JavaScript allocates memory for all of them upfront — even if you only need the first 10.

const million = Array.from({ length: 1_000_000 }, (_, i) => i);
console.log(million[0]); // 0 — but you just allocated 1M slots
Enter fullscreen mode Exit fullscreen mode

Generators solve this by producing values lazily — only when you ask for the next one.


⚙️ The Iterator Protocol

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

const iter = [1, 2, 3][Symbol.iterator]();

iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
iter.next(); // { value: 3, done: false }
iter.next(); // { value: undefined, done: true }
Enter fullscreen mode Exit fullscreen mode

Anything that implements this protocol works with for...of, spread [...], and destructuring.


⚙️ Generator Functions

A generator function uses function* and yield to pause and resume execution.

function* count() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = count();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }
Enter fullscreen mode Exit fullscreen mode

Each yield pauses the function. The next .next() call resumes it from exactly where it left off.


⚙️ Infinite Sequences — The Real Power

Generators shine when the sequence has no fixed end.

function* naturals(start = 0) {
  while (true) {
    yield start++;
  }
}

function take(n, iter) {
  const result = [];
  for (const val of iter) {
    result.push(val);
    if (result.length === n) break;
  }
  return result;
}

take(5, naturals(1)); // [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

No memory wasted. No upfront computation. The sequence runs only as far as you pull it.


⚙️ Passing Values In

next(value) lets you send data back into a paused generator.

function* adder() {
  let total = 0;
  while (true) {
    const n = yield total;
    total += n;
  }
}

const acc = adder();
acc.next();    // { value: 0, done: false }  — primes the generator
acc.next(10);  // { value: 10, done: false }
acc.next(5);   // { value: 15, done: false }
acc.next(20);  // { value: 35, done: false }
Enter fullscreen mode Exit fullscreen mode

✅ Real-World Use Cases

Use case Why generators help
Paginated API fetching Pull next page only when needed
Infinite scroll data Produce IDs lazily
Custom iteration order Full control over for...of
Async coordination async function* for async streams
State machines Each yield is a state transition

⚠️ Gotchas

Generators are single-pass. Once exhausted, they don't reset.

const gen = count();
[...gen]; // [1, 2, 3]
[...gen]; // [] — already done
Enter fullscreen mode Exit fullscreen mode

You can't use yield inside a regular callback inside a generator.

function* broken() {
  [1, 2, 3].forEach(n => yield n); // ❌ SyntaxError
}

function* works() {
  yield* [1, 2, 3]; // ✅ delegate to another iterable
}
Enter fullscreen mode Exit fullscreen mode

Use yield* to delegate to another iterable — it flattens it into the outer generator.


🔍 Flow

Call generator fn → Get iterator object → Call .next() → Resume from yield → Return { value, done }
  ├─ done: false → yield produces value → pauses
  └─ done: true → function returned or threw
Enter fullscreen mode Exit fullscreen mode

Final Takeaway: Generators let you describe a sequence without computing it all upfront. Reach for them when you need infinite sequences, lazy pipelines, or fine-grained control over iteration. They're the foundation of async iterators (for await...of) too.

Summary

Concept What it is
Iterator protocol Object with .next() returning { value, done }
function* Declares a generator function
yield Pauses execution, emits a value
yield* Delegates to another iterable
next(val) Resumes generator and passes a value in
for...of Consumes any iterable automatically

Originally published on Bitlyst.

Top comments (0)