Skip to content

Generators

This lesson explains Generators with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.

ES6 Generators Overview

Generators are special ES6 functions that can pause and resume execution. A generator function uses function* syntax and returns a generator object. Values are produced one at a time using the yield keyword.

Generators are useful for custom iterators, lazy data generation, pagination, infinite sequences, step-by-step workflows, state machines, testing utilities, and advanced asynchronous control patterns.

Concept Description Syntax
Generator Function Function that can pause and resume. function* name() { ... }
yield Pauses execution and returns a value. yield value
next() Resumes generator execution. generator.next()
done Indicates whether generator is finished. { value, done }
Iterable Generator objects work with for...of. for (const item of generator)

Basic Generator Function

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

const generator =
  numbers();

console.log(
  generator.next()
);

console.log(
  generator.next()
);

console.log(
  generator.next()
);

Each call to next() resumes the generator until the next yield.

Understanding next() Result

function* greet() {
  yield "Hello";
  yield "Welcome";
}

const iterator =
  greet();

console.log(
  iterator.next()
);

// { value: "Hello", done: false }

console.log(
  iterator.next()
);

// { value: "Welcome", done: false }

console.log(
  iterator.next()
);

// { value: undefined, done: true }

The next() method returns an object with value and done.

Generator Return Value

function* getValues() {
  yield "A";
  yield "B";

  return "Finished";
}

const generator =
  getValues();

console.log(
  generator.next()
);

console.log(
  generator.next()
);

console.log(
  generator.next()
);

A return value appears in the final next() result, but it is not included in for...of iteration.

Using Generators with for...of

function* skills() {
  yield "HTML";
  yield "CSS";
  yield "JavaScript";
}

for (const skill of skills()) {
  console.log(skill);
}

Generator objects are iterable, so they work directly with for...of.

Convert Generator to Array

function* colors() {
  yield "Red";
  yield "Green";
  yield "Blue";
}

const list =
  [...colors()];

console.log(list);

// ["Red", "Green", "Blue"]

The spread operator can collect yielded values into an array.

Generator with Array.from()

function* ids() {
  yield 101;
  yield 102;
  yield 103;
}

const result =
  Array.from(
    ids()
  );

console.log(result);

Lazy Number Sequence

function* createNumbers() {
  let count =
    1;

  while (count <= 5) {
    yield count;

    count += 1;
  }
}

for (const value of createNumbers()) {
  console.log(value);
}

Generators create values only when needed, making them useful for lazy sequences.

Infinite Generator

function* infiniteIds() {
  let id =
    1;

  while (true) {
    yield id;

    id += 1;
  }
}

const ids =
  infiniteIds();

console.log(
  ids.next().value
);

console.log(
  ids.next().value
);

console.log(
  ids.next().value
);

Infinite generators are safe when values are requested one at a time. Avoid converting infinite generators into arrays.

Passing Values into Generators

function* conversation() {
  const name =
    yield "What is your name?";

  yield "Hello " + name;
}

const chat =
  conversation();

console.log(
  chat.next().value
);

console.log(
  chat.next("Alice").value
);

Values passed to next(value) become the result of the previous yield expression.

Delegating with yield*

function* frontend() {
  yield "HTML";
  yield "CSS";
}

function* fullstack() {
  yield* frontend();

  yield "JavaScript";
  yield "Node.js";
}

console.log(
  [...fullstack()]
);

The yield* syntax delegates to another iterable or generator.

Custom Iterator with Generator

const userCollection =
  {
    users:
      [
        "Alice",
        "Bob",
        "Sarah"
      ],

    *[Symbol.iterator]() {
      for (const user of this.users) {
        yield user;
      }
    }
  };

for (const user of userCollection) {
  console.log(user);
}

Generators make custom iterable objects easier to create.

Range Generator

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

console.log(
  [...range(1, 5)]
);

// [1, 2, 3, 4, 5]

Range generators are useful for pagination, counters, testing, and UI lists.

Pagination Generator

function* paginate(items, pageSize) {
  for (
    let index = 0;
    index < items.length;
    index += pageSize
  ) {
    yield items.slice(
      index,
      index + pageSize
    );
  }
}

const products =
  [
    "Laptop",
    "Phone",
    "Tablet",
    "Monitor",
    "Mouse"
  ];

for (
  const page of paginate(products, 2)
) {
  console.log(page);
}

This generator returns one page of data at a time.

Unique ID Generator

function* idGenerator(prefix = "ID") {
  let count =
    1;

  while (true) {
    yield prefix + "-" + count;

    count += 1;
  }
}

const ids =
  idGenerator("USER");

console.log(
  ids.next().value
);

console.log(
  ids.next().value
);

State Machine Example

function* trafficLight() {
  while (true) {
    yield "green";
    yield "yellow";
    yield "red";
  }
}

const light =
  trafficLight();

console.log(light.next().value);
console.log(light.next().value);
console.log(light.next().value);
console.log(light.next().value);

Generators can model repeating state transitions.

Error Handling in Generators

function* processSteps() {
  try {
    yield "Step 1";
    yield "Step 2";
  } catch (error) {
    yield "Error: " + error.message;
  }
}

const steps =
  processSteps();

console.log(
  steps.next().value
);

console.log(
  steps.throw(
    new Error("Failed")
  ).value
);

The throw() method sends an error into the generator.

Stopping a Generator with return()

function* tasks() {
  yield "Task 1";
  yield "Task 2";
  yield "Task 3";
}

const taskList =
  tasks();

console.log(
  taskList.next()
);

console.log(
  taskList.return("Stopped")
);

console.log(
  taskList.next()
);

The return() method stops the generator early.

Generator vs Regular Function

Feature Regular Function Generator Function
Syntax function name() function* name()
Execution Runs completely when called. Pauses and resumes.
Returns Final value. Generator object.
Produces Multiple Values No. Yes, using yield.
Best Use Normal calculations. Lazy sequences and iterators.

Generator vs Iterator

Iterator Generator
Manual object with next(). Created by function*.
More boilerplate code. Less boilerplate code.
Must manually track state. State is automatically remembered.
Can be harder to read. Usually cleaner and more readable.

Real-World Generator Use Cases

  • Creating custom iterators for objects.
  • Generating unique IDs.
  • Building lazy sequences.
  • Handling paginated data.
  • Modeling state machines.
  • Creating test data step by step.
  • Processing large datasets lazily.
  • Implementing infinite sequences safely.
  • Delegating iteration using yield*.
  • Building reusable iteration utilities.

Common Generator Mistakes

  • Forgetting the * in function*.
  • Expecting a generator to run immediately when called.
  • Forgetting to call next().
  • Confusing yield with return.
  • Converting infinite generators into arrays.
  • Ignoring the done property.
  • Expecting for...of to include the final return value.
  • Overusing generators where simple arrays or functions are clearer.

Generator Best Practices

  • Use generators when values should be produced lazily.
  • Use for...of for simple generator iteration.
  • Use yield* to delegate to another iterable.
  • Avoid using generators for simple one-time calculations.
  • Do not convert infinite generators into arrays.
  • Keep generator logic small and readable.
  • Use descriptive generator names.
  • Handle early termination when needed with return().

Generator Interview Questions

  • What is a generator function?
  • What is the purpose of yield?
  • What does next() return?
  • How is a generator different from a regular function?
  • What is yield*?
  • Can generators create infinite sequences?
  • How do generators work with for...of?
  • What is the difference between yield and return?
  • How do you stop a generator early?
  • What are real-world generator use cases?

Key Takeaways

  • Generator functions use function* syntax.
  • yield pauses execution and returns a value.
  • next() resumes the generator.
  • Generators remember their internal state between calls.
  • Generators are iterable and work with for...of.
  • Use generators for lazy sequences, custom iterators, pagination, and state machines.

Pro Tip

In interviews, explain generators as functions that can pause at yield and resume with next(). They are best used when data should be produced step by step instead of all at once.