Articles

JavaScript Generators and Iterators, Explained

Generators are functions that pause and resume with the yield keyword, producing values lazily on demand instead of computing them all at once.

Takina Takina · · 4 min read
Code editor showing JavaScript syntax

A JavaScript generator is a function that can pause its own execution with yield and resume later from exactly where it left off, producing a sequence of values one at a time instead of computing and returning them all at once. Generators are the mechanism behind JavaScript’s iterator protocol, and they show up anywhere you need lazy sequences, custom loops, or — historically — a cleaner way to write asynchronous code before async/await existed.

The iterator protocol

Before generators make sense, it helps to know what they implement. An iterable is any object with a Symbol.iterator method that returns an iterator — an object with a next() method returning { value, done }. Arrays, strings, Map, and Set are all built-in iterables, which is why for...of works on them:

const arr = [10, 20, 30];
const it = arr[Symbol.iterator]();
it.next(); // { value: 10, done: false }
it.next(); // { value: 20, done: false }

Writing this protocol by hand for a custom object is tedious — you have to manually track state between calls. Generators exist to make that trivial.

Writing a generator

A generator function is declared with function*, and calling it doesn’t run the body immediately — it returns a generator object, which is itself an iterator:

function* countUp(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

const counter = countUp(3);
counter.next(); // { value: 1, done: false }
counter.next(); // { value: 2, done: false }
counter.next(); // { value: 3, done: false }
counter.next(); // { value: undefined, done: true }

Each call to next() runs the function body until it hits a yield, then pauses, preserving all local state — variables, loop position, everything — until next() is called again. Because generator objects are iterable, they work directly with for...of:

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

Why laziness matters

The core advantage of a generator is that values are produced on demand, not computed up front. This makes infinite sequences practical:

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

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

take(naturals(), 5); // [1, 2, 3, 4, 5]

An eager version of naturals() would never return — it would try to build an infinite array before yielding anything. The generator version only does exactly as much work as the consumer asks for, which is the same principle behind lazy evaluation in data pipelines and streaming APIs.

Passing values back in

yield is a two-way channel. The value passed to next() becomes the result of the yield expression inside the generator:

function* echo() {
  const first = yield "ready";
  console.log("received:", first);
}

const gen = echo();
gen.next();        // { value: "ready", done: false }
gen.next("hello"); // logs "received: hello"

This is the mechanism that let libraries simulate async/await before it was native: a generator would yield a promise, and a runner function would resolve it and feed the result back in with next(value), repeating until the generator completed. Native async/await does the same thing under the hood but with syntax and a runner built into the language, which is why it fully replaced that pattern for asynchronous code.

Generators vs closures for custom iteration

A closure can also produce a stateful sequence — a function that remembers a counter between calls, for example — but it has to be called explicitly each time and doesn’t participate in for...of or spread syntax unless you also implement the iterator protocol by hand. A generator gets both the paused-state behavior of a closure and iterable-by-default integration with the rest of the language for free.

ClosuresGenerators
Pauses mid-executionNoYes, at each yield
Works with for...of / spreadOnly with manual Symbol.iteratorAutomatically
Two-way value passingManualBuilt in (next(value))
Best forEncapsulated state, memoizationSequences, lazy pipelines, custom iteration

Where generators show up today

Native async/await has taken over most of the asynchronous-flow use cases generators were originally introduced for, but generators remain the standard way to implement custom iterables — objects you want to work with for...of, array destructuring, or spread syntax — and they’re used internally by libraries that need cancellable, resumable computation, such as certain state-machine and effect-handling patterns in front-end frameworks. If you’re implementing a data structure and want it to behave like a native collection in a for...of loop, a generator method is almost always the simplest path there.

The takeaway

A generator is a function that pauses at yield and resumes on the next call to next(), and every generator object automatically implements the iterator protocol that powers for...of and spread syntax. Use generators when you need lazy, on-demand sequences or a clean way to make a custom object iterable — reach for a plain function or closure when you just need to encapsulate state without pausing mid-execution.

Takina Takina · · 5 min read

Promise.all() vs allSettled() vs race() Compared

Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

JavaScript Spread vs Rest Operators, Explained

The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

ResizeObserver API Explained: Watching Element Size

The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.

#Web Development #JavaScript #Frontend