← Back to all tutorials

Generators

Learn generator functions — pausable functions with function* and yield that produce values on demand.

Generators

Generators are special functions that can be paused and resumed. Instead of returning a single value, they produce a sequence of values on demand using the yield keyword.

Basic Syntax

// Generator function (note the *)
function* myGenerator() {
    yield "first";
    yield "second";
    yield "third";
}

// Create a generator object
const gen = myGenerator();

// Call .next() to get each value:
gen.next();  // { value: "first", done: false }
gen.next();  // { value: "second", done: false }
gen.next();  // { value: "third", done: false }
gen.next();  // { value: undefined, done: true }

Each yield pauses the function and returns a value. Calling .next() resumes from where it paused.

How Generators Work

  1. Calling myGenerator() doesn't execute the body — it returns a generator object
  2. Each .next() runs the code until the next yield
  3. The function pauses at each yield
  4. When there are no more yield statements, done becomes true

Generators are Iterables

function* colors() {
    yield "red";
    yield "green";
    yield "blue";
}

// for...of automatically calls .next()
for (const color of colors()) {
    console.log(color);
}
// "red"
// "green"
// "blue"

// Spread operator works too
const allColors = [...colors()];
// ["red", "green", "blue"]

// Destructuring
const [first, second] = colors();
// first = "red", second = "green"

Infinite Sequences

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

const nums = naturalNumbers();
nums.next().value;  // 1
nums.next().value;  // 2
nums.next().value;  // 3
// Goes on forever — but only computes on demand!

Without generators, an infinite loop would freeze your program. With generators, values are computed lazily — only when requested.

Passing Values INTO a Generator

function* conversation() {
    const name = yield "What is your name?";
    const age = yield `Hello, ${name}! How old are you?`;
    yield `${name}, age ${age}. Got it!`;
}

const chat = conversation();

chat.next();           // { value: "What is your name?", done: false }
chat.next("Alice");    // { value: "Hello, Alice! How old are you?", done: false }
chat.next(25);         // { value: "Alice, age 25. Got it!", done: false }
chat.next();           // { value: undefined, done: true }

The argument you pass to .next(value) becomes the return value of the yield expression inside the generator.

Practical: ID Generator

function* idGenerator(prefix = "id") {
    let id = 1;
    while (true) {
        yield `${prefix}_${id++}`;
    }
}

const userId = idGenerator("user");
userId.next().value;  // "user_1"
userId.next().value;  // "user_2"
userId.next().value;  // "user_3"

const orderId = idGenerator("order");
orderId.next().value;  // "order_1"
orderId.next().value;  // "order_2"

Practical: Range Function

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

[...range(1, 5)];       // [1, 2, 3, 4, 5]
[...range(0, 10, 2)];   // [0, 2, 4, 6, 8, 10]
[...range(10, 50, 10)]; // [10, 20, 30, 40, 50]

yield* — Delegating to Another Generator

function* inner() {
    yield "a";
    yield "b";
}

function* outer() {
    yield 1;
    yield* inner();  // Delegate to inner generator
    yield 2;
}

[...outer()];  // [1, "a", "b", 2]

yield* delegates to another iterable (generator, array, string) and yields each of its values.

Generator vs Regular Function

FeatureRegular FunctionGenerator
ExecutionRuns to completionPausable and resumable
Return valuesOne value (or array)Multiple values on demand
Syntaxfunction name()function* name()
Keywordreturnyield (and return)
IterableNoYes
Infinite sequencesCannotCan (lazy evaluation)

Key Takeaways

  • Generators use function* and yield to produce values on demand
  • .next() returns { value, done } — resumes execution until the next yield
  • Generators are iterable — work with for...of, spread, and destructuring
  • They enable infinite sequences with lazy evaluation
  • Values can be passed into generators via .next(value)
  • yield* delegates to another generator or iterable
  • Common uses: ID generators, range functions, pagination, async flow control