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.
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.
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.