---
title: Generators
source: 'https://academia.sh/en/courses/javascript-object-model/generators'
course: 'Objects and Functions in JavaScript'
language: en
updated: '2026-08-17T18:09:49+00:00'
license: 'CC BY-SA 4.0'
---

# Generators

Pausable functions, the generator object's place in the iteration protocol, endless sequences, delegation, two-way communication, and lazy pipelines.

In the previous lesson, the same three pieces kept repeating in hand-written iterators:
the variable holding position, the end condition, and the self-returning
`[Symbol.iterator]` method. All three are mechanical and overshadow the function's real
job.

A **generator** is a function that can pause its execution and resume later from where it
left off. It is defined in the `function*` form and pauses by giving a value with the
`yield` statement. When called, its body does not run; it returns an object satisfying
both of the protocol's contracts. The lesson opens up this object's behavior and reduces
the previous lesson's lazy series to a few lines.

## A Pausable Function

A generator function's body does not run at all until the first `next` call. Every
`next` advances to the next `yield` statement and stops there.

```js
let computeCount = 0;

function* calibratedSeries(rawValues, offset) {
  for (const raw of rawValues) {
    computeCount += 1;
    yield Number((raw + offset).toFixed(2));
  }
}

const generator = calibratedSeries([21.4, 19.8, 25.1], -0.4);

console.log(computeCount);
console.log(typeof generator.next);
console.log(generator[Symbol.iterator]() === generator);

console.log(JSON.stringify(generator.next()));
console.log(computeCount);
console.log(JSON.stringify(generator.next()));
console.log(JSON.stringify(generator.next()));
console.log(JSON.stringify(generator.next()));
console.log(computeCount);

console.log([...calibratedSeries([21.4, 19.8], -0.4)].join(","));
console.log(typeof calibratedSeries);
```

```
0
function
true
{"value":21,"done":false}
1
{"value":19.4,"done":false}
{"value":24.7,"done":false}
{"done":true}
3
21,19.4
function
```

The first line shows the body has not run at all. The third line confirms the generator
object is both iterator and iterable: it carries a `[Symbol.iterator]` method that
returns itself. All three pieces written by hand in the previous lesson are produced here
by the language itself.

The fifth line measures laziness: the first `next` has done only one computation. Once
the values run out, the `done` field becomes true and the `value` field stays
`undefined`. The last line fixes a distinction — the generator function itself is an
ordinary function; what is a generator is the object the call returns.

A generator object gets exhausted too. Traversing it again requires calling the function
again; the spread in the tenth line sets up a new generator for exactly this.

## Generator as a Class Method

A generator can also be defined in a class body by putting an asterisk before the method
name. The `MeasurementSeries` class from the previous lesson is shortened by half this
way.

```js
class MeasurementSeries {
  constructor(sensor, values, offset = 0) {
    this.sensor = sensor;
    this.values = values;
    this.offset = offset;
  }

  *[Symbol.iterator]() {
    for (const raw of this.values) {
      yield Number((raw + this.offset).toFixed(2));
    }
  }

  *labeledPairs() {
    let index = 0;
    for (const value of this) {
      yield [`${this.sensor}#${index}`, value];
      index += 1;
    }
  }
}

const series = new MeasurementSeries("S-01", [21.4, 19.8, 25.1], -0.4);

console.log([...series].join(","));
console.log([...series].join(","));

for (const [label, value] of series.labeledPairs()) {
  console.log(`${label} -> ${value}`);
}

console.log(Object.getOwnPropertyNames(MeasurementSeries.prototype).join(","));
console.log(Object.hasOwn(MeasurementSeries.prototype, Symbol.iterator));
console.log(new Map(series.labeledPairs()).get("S-01#1"));
```

```
21,19.4,24.7
21,19.4,24.7
S-01#0 -> 21
S-01#1 -> 19.4
S-01#2 -> 24.7
constructor,labeledPairs
true
19.4
```

The series can be traversed twice, because what is being traversed is the series itself
and every traversal produces a new generator object. The `labeledPairs` method writes
`for (const value of this)` and benefits from its own class's iterability; it does not
access the raw data a second time.

The seventh and eighth lines repeat the tie to the chain: generator methods also stand on
the prototype, and being symbol-keyed, they do not enter `Object.keys` output. The last
line shows the protocol's unifying power — any iterable object giving key–value pairs can
be handed directly to the `Map` constructor.

## Endless Sequences and Delegation

Because a generator only runs when requested, defining an endless sequence is no problem.
`yield*` lets a generator delegate its work to another iterable object.

```js
function* increasingTime(start, step) {
  let time = start;
  while (true) {
    yield time;
    time += step;
  }
}

function* firstN(iterable, count) {
  if (count <= 0) return;
  let remaining = count;
  for (const value of iterable) {
    yield value;
    remaining -= 1;
    if (remaining <= 0) return;
  }
}

console.log([...firstN(increasingTime(1000, 60), 4)].join(","));

function* frontSeries() {
  yield 21.4;
  yield 19.8;
}

function* backSeries() {
  yield 25.1;
}

function* allSeries() {
  yield* frontSeries();
  yield* backSeries();
  yield* [18.2, 30.0];
  return "done";
}

const combined = allSeries();
console.log([...allSeries()].join(","));

let step = combined.next();
const values = [];
while (!step.done) {
  values.push(step.value);
  step = combined.next();
}
console.log(values.length);
console.log(step.value);
```

```
1000,1060,1120,1180
21.4,19.8,25.1,18.2,30
5
done
```

`increasingTime` contains an infinite loop, but the program does not hang: values are
produced only when requested, and `firstN` stops after the fourth value. The infinite
loop being harmless is a direct result of laziness; producing the same measurement times
in an array would be impossible.

`yield*` accepts any kind of iterable object — another generator or an ordinary array.
The value of the `return` statement is **skipped** by `for...of` and spread; only code
that manually continues the `next` calls can see it, in the finishing step's `value`
field. This is why generators are used to stream values, not to return a result.

## Two-Way Communication

The argument given to a `next` call becomes the value of the `yield` expression the
generator is paused at. This way, a generator does not just produce values, it also
receives values from outside.

```js
function* thresholdMonitor(startThreshold) {
  let threshold = startThreshold;
  const exceeding = [];
  try {
    while (true) {
      const incoming = yield `threshold=${threshold}, exceeding=${exceeding.length}`;
      if (typeof incoming === "number") {
        if (incoming > threshold) exceeding.push(incoming);
      } else if (typeof incoming === "object" && incoming !== null) {
        threshold = incoming.newThreshold;
      }
    }
  } finally {
    console.log(`closing: total exceeding=${exceeding.length}`);
  }
}

const monitor = thresholdMonitor(20);

console.log(monitor.next("this value is ignored").value);
console.log(monitor.next(21.4).value);
console.log(monitor.next(19.8).value);
console.log(monitor.next({ newThreshold: 25 }).value);
console.log(monitor.next(25.1).value);
console.log(JSON.stringify(monitor.return("manually stopped")));
console.log(JSON.stringify(monitor.next(30)));
```

```
threshold=20, exceeding=0
threshold=20, exceeding=1
threshold=20, exceeding=1
threshold=25, exceeding=1
threshold=25, exceeding=2
closing: total exceeding=2
{"value":"manually stopped","done":true}
{"done":true}
```

The first `next` call's argument is ignored: the body has not yet paused at any `yield`
expression, so there is no place to receive a value. In subsequent calls, the value sent
falls into the `incoming` variable.

The `return` method ends the generator from outside and runs the `finally` block — this
is the generator counterpart of the previous lesson's cleanup point. A finished generator
answers as done on subsequent `next` calls. The `throw` method similarly raises an error
at the point the generator is paused.

Compared with the counter in the Closures lesson, the difference is clear: a closure
holds state but does not hold execution. A generator, alongside state, also stores
**where in the program it left off**.

## A Lazy Pipeline

Generators' most concrete gain is that transformation chains can be built without
producing intermediate arrays. When filtering and mapping are each written as a
generator, every value passes through the entire pipeline one at a time.

```js
let readCount = 0;
let transformCount = 0;

function* rawSource(values) {
  for (const value of values) {
    readCount += 1;
    yield value;
  }
}

function* filterGen(iterable, predicate) {
  for (const value of iterable) {
    if (predicate(value)) yield value;
  }
}

function* mapGen(iterable, transform) {
  for (const value of iterable) {
    transformCount += 1;
    yield transform(value);
  }
}

function* firstN(iterable, count) {
  if (count <= 0) return;
  let remaining = count;
  for (const value of iterable) {
    yield value;
    remaining -= 1;
    if (remaining <= 0) return;
  }
}

const raw = [21.4, 19.8, 25.1, 18.2, 30.0, 27.3, 15.0];
const pipeline = firstN(
  mapGen(
    filterGen(rawSource(raw), (d) => d > 20),
    (d) => Number((d * 1.8 + 32).toFixed(1)),
  ),
  2,
);

console.log(readCount);
console.log([...pipeline].join(","));
console.log(readCount);
console.log(transformCount);

const withArrays = raw
  .filter((d) => d > 20)
  .map((d) => Number((d * 1.8 + 32).toFixed(1)))
  .slice(0, 2);
console.log(withArrays.join(","));
```

```
0
70.5,77.2
3
2
70.5,77.2
```

The two paths give the same result; the work they do is different. The generator
pipeline reads only three of seven raw values and does two transforms; it stops the
moment the two needed results are obtained. The version written with array methods
first filters all five values, then transforms all five, then takes the first two —
and produces two intermediate arrays along the way.

The difference sharpens as the data source grows or the transform gets more expensive.
The generator setup, in exchange, has a fixed cost: several execution passes happen per
value. On small arrays, array methods are both shorter and faster; the criterion is the
number of unprocessed elements.

## Summary

- A generator function's body does not run when called; it returns a generator object
  that is both iterator and iterable.
- Every `next` call advances to the next `yield` statement; execution pauses there and
  state is preserved.
- A starred method can be defined in a class body; generator methods stand on the
  prototype too.
- `yield*` delegates work to another iterable object; the `return` statement's value is
  visible only in manually continued `next` calls.
- The `next` argument becomes the value of the `yield` expression the generator is
  paused at; the `return` method performs cleanup by running the `finally` block.
- A pipeline built from generators produces no intermediate array and processes only as
  many values as requested.

## Next Step

All the collections in this topic have held arbitrary JavaScript values: numbers,
strings, objects. When a measurement stream needs to be written to a file or sent over a
network, though, the data has to be represented as a byte sequence. The next lesson takes
up fixed-size binary buffers and the typed views over them; byte order and overflow
behavior will be tested there too.
