Generators are a special type of function that can pause and resume execution. They provide a cleaner, more intuitive way to write iterators and manage asynchronous flows. Introduced in ES6, generators use the function* syntax and the yield keyword to produce a sequence of values on demand.
A generator function returns a generator object that conforms to both the iterator and iterable protocols. Unlike regular functions that run to completion, generators can yield multiple values over time.
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }Key differences from regular functions:
- Declared with
function*(note the asterisk) - Use
yieldto pause and return a value - Execution can be resumed from where it left off
- Returns a generator object, not a single value
function* countUpTo(max) {
let count = 1;
while (count <= max) {
yield count;
count++;
}
}
const counter = countUpTo(5);
for (const num of counter) {
console.log(num); // 1, 2, 3, 4, 5
}function* alphabet() {
yield "a";
yield "b";
yield "c";
}
// Can use for...of
for (const letter of alphabet()) {
console.log(letter); // a, b, c
}
// Can use spread
console.log([...alphabet()]); // ["a", "b", "c"]
// Can destructure
const [first, second] = alphabet();
console.log(first, second); // a, bfunction* pauseAndResume() {
console.log("Start");
yield "First pause";
console.log("Resumed");
yield "Second pause";
console.log("Resumed again");
return "Done";
}
const gen = pauseAndResume();
console.log("--- Calling next() ---");
console.log(gen.next());
// "Start"
// { value: "First pause", done: false }
console.log("--- Calling next() ---");
console.log(gen.next());
// "Resumed"
// { value: "Second pause", done: false }
console.log("--- Calling next() ---");
console.log(gen.next());
// "Resumed again"
// { value: "Done", done: true }
console.log("--- Calling next() ---");
console.log(gen.next());
// { value: undefined, done: true }Execution flow:
- Calling
generator()doesn't execute the body — it returns a generator object - Each
next()call runs the generator until the nextyield - The generator's state (local variables) is preserved between calls
- When
returnis hit (or the function ends),donebecomestrue
function* infiniteCounter(start = 0) {
let count = start;
while (true) {
yield count++;
}
}
const counter = infiniteCounter(1);
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
// Can keep calling forever...
// Use with for...of (must break manually!)
for (const num of infiniteCounter(10)) {
if (num > 15) break;
console.log(num); // 10, 11, 12, 13, 14, 15
}function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
console.log([...range(1, 10)]); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log([...range(0, 20, 5)]); // [0, 5, 10, 15, 20]
console.log([...range(10, 1, -1)]); // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]function* idGenerator(prefix = "ID") {
let count = 1;
while (true) {
yield `${prefix}-${String(count).padStart(4, "0")}`;
count++;
}
}
const userIds = idGenerator("USER");
console.log(userIds.next().value); // "USER-0001"
console.log(userIds.next().value); // "USER-0002"
console.log(userIds.next().value); // "USER-0003"Generators can both produce and consume values. The argument passed to next() becomes the value of the previous yield expression.
function* twoWay() {
const name = yield "What is your name?";
const age = yield `Hello ${name}! How old are you?`;
yield `${name}, you will be ${parseInt(age) + 1} next year!`;
}
const gen = twoWay();
console.log(gen.next().value); // "What is your name?"
console.log(gen.next("Alice").value); // "Hello Alice! How old are you?"
console.log(gen.next("25").value); // "Alice, you will be 26 next year!"How it works:
- First
next()starts the generator, runs until firstyield - Second
next("Alice")resumes, assigning "Alice" to the firstyieldexpression - Third
next("25")resumes, assigning "25" to the secondyieldexpression
function* taskRunner() {
const task1 = yield "Start task 1";
console.log(`Task 1 result: ${task1}`);
const task2 = yield "Start task 2";
console.log(`Task 2 result: ${task2}`);
const task3 = yield "Start task 3";
console.log(`Task 3 result: ${task3}`);
return "All tasks complete";
}
const runner = taskRunner();
let result = runner.next();
while (!result.done) {
console.log(result.value);
// Simulate async task completion
result = runner.next(`Completed: ${result.value}`);
}
console.log(result.value);Use yield* to delegate iteration to another iterable (generator, array, string, etc.).
function* generatorA() {
yield "A1";
yield "A2";
}
function* generatorB() {
yield "B1";
yield* generatorA(); // Delegate to generatorA
yield "B2";
}
for (const value of generatorB()) {
console.log(value);
}
// B1
// A1
// A2
// B2function* combined() {
yield "Start";
yield* [1, 2, 3];
yield "Middle";
yield* "abc";
yield "End";
}
console.log([...combined()]);
// ["Start", 1, 2, 3, "Middle", "a", "b", "c", "End"]class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
}
function* traverseDepthFirst(node) {
yield node.value;
for (const child of node.children) {
yield* traverseDepthFirst(child);
}
}
const tree = new TreeNode("root", [
new TreeNode("child1", [
new TreeNode("grandchild1"),
new TreeNode("grandchild2")
]),
new TreeNode("child2")
]);
for (const value of traverseDepthFirst(tree)) {
console.log(value);
}
// root, child1, grandchild1, grandchild2, child2function* counter() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log("Cleanup!");
}
}
const gen = counter();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return(99)); // { value: 99, done: true }
console.log("Cleanup!"); // Runs due to finally
console.log(gen.next()); // { value: undefined, done: true }function* errorHandler() {
try {
yield "Start";
yield "Middle"; // This line is never reached
} catch (error) {
yield `Caught: ${error.message}`;
}
}
const gen = errorHandler();
console.log(gen.next()); // { value: "Start", done: false }
console.log(gen.throw(new Error("Oops!"))); // { value: "Caught: Oops!", done: false }| Feature | Regular Iterator | Generator |
|---|---|---|
| Syntax | Manual next() method |
function* + yield |
| State management | Manual | Automatic |
| Code readability | Verbose | Clean and linear |
| Two-way communication | Possible | Built-in |
| Error handling | Manual | try...catch with throw() |
| Use case | Simple iteration | Complex sequences, async flows |
function createRangeIterator(start, end) {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}Generators compute values on demand, saving memory:
function* largeDataset() {
for (let i = 0; i < 1000000; i++) {
yield expensiveComputation(i);
}
}
// Only computes what's needed
for (const item of largeDataset()) {
if (item > 100) break;
}function* paginatedFetch(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = yield fetch(`${url}?page=${page}`);
const data = yield response.json();
yield* data.items;
hasMore = data.hasMore;
page++;
}
}function* uniqueIdGenerator() {
let id = 0;
while (true) {
yield ++id;
}
}
const ids = uniqueIdGenerator();
const users = [
{ id: ids.next().value, name: "Alice" },
{ id: ids.next().value, name: "Bob" },
{ id: ids.next().value, name: "Charlie" }
];function* gen() { yield 1; }
// ❌ Missing parentheses — returns generator function, not generator
for (const val of gen) { ... }
// ✅ Correct — calling the function returns the generator object
for (const val of gen()) { ... }function* echo() {
const value = yield "Ready";
console.log(value);
}
const gen = echo();
// ❌ First next() must have no argument
// gen.next("Hello"); // Would assign "Hello" before any yield!
// ✅ First next() starts the generator
gen.next(); // { value: "Ready", done: false }
gen.next("Hello"); // Logs "Hello"function* withReturn() {
yield 1;
yield 2;
return 3; // for...of ignores this!
}
const values = [...withReturn()];
console.log(values); // [1, 2] — 3 is excluded!function* infinite() {
let i = 0;
while (true) yield i++;
}
// ❌ This hangs forever!
// console.log([...infinite()]);
// ✅ Always provide an exit condition
for (const num of infinite()) {
if (num >= 10) break;
console.log(num);
}Create a generator that yields array elements in reverse.
function* reverse(arr) {
// Your code
}
console.log([...reverse([1, 2, 3, 4, 5])]); // [5, 4, 3, 2, 1]Create a generator that yields chunks of an array.
function* chunks(arr, size) {
// Your code
}
console.log([...chunks([1, 2, 3, 4, 5, 6], 2)]);
// [[1, 2], [3, 4], [5, 6]]Combine two generators by alternating their values.
function* interleave(gen1, gen2) {
// Your code
}
const a = function* () { yield* [1, 3, 5]; };
const b = function* () { yield* [2, 4, 6]; };
console.log([...interleave(a(), b())]);
// [1, 2, 3, 4, 5, 6]Create an infinite generator that yields prime numbers.
function* primes() {
// Your code
}
const primeGen = primes();
for (let i = 0; i < 10; i++) {
console.log(primeGen.next().value);
}
// 2, 3, 5, 7, 11, 13, 17, 19, 23, 29Use a generator to implement a simple state machine.
function* trafficLight() {
// States: green → yellow → red → green
// Yield each state, advance on next()
}
const light = trafficLight();
console.log(light.next().value); // "green"
console.log(light.next().value); // "yellow"
console.log(light.next().value); // "red"
console.log(light.next().value); // "green" (cycles back)- Generators use
function*syntax andyieldto produce sequences - They pause at
yieldand resume onnext() - Generator objects conform to both iterator and iterable protocols
- Values can be sent into generators via
next(value) yield*delegates iteration to another iterablereturn()terminates early;throw()injects errors- Generators provide cleaner syntax than manual iterators
- Perfect for lazy evaluation, infinite sequences, and complex state machines
Generators connect to:
- Async Generators —
async function*for async iteration - Iterators — the underlying protocol generators use
- Promises/Async-Await — combining with async operations
Happy coding! 🚀