JavaScript 53 🧬 Generators — function*, yield
function* simpleGen() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGen();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
for (const value of simpleGen()) {
console.log(value);
}
console.log([...simpleGen()]);
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range(1, 5)]);
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
function* twoWay() {
const name = yield 'What is your name?';
const age = yield `Hello, ${name}! How old are you?`;
yield `${name} is ${age} years old.`;
}
const dialog = twoWay();
console.log(dialog.next().value);
console.log(dialog.next('Alice').value);
console.log(dialog.next(30).value);
function* delegate() {
yield 1;
yield* [2, 3];
yield 4;
}
console.log([...delegate()]);
A generator is a function that can pause and resume. Where a regular function runs to completion, a generator produces a sequence of values, one at a time, on demand. The function* syntax and yield keyword make this possible.
Key point: Generators return an iterator. You pull values out one at a time with .next(), or iterate over them with for...of. Between calls, the generator’s entire state is preserved — variables, position, everything — until you ask for the next value.
a – What is a generator
A generator is a special function declared with function*. When called, it doesn’t execute its body — it returns a generator object that you can step through.
Basic syntax:
function* simpleGen() {
yield 1;
yield 2;
yield 3;
}
Calling it doesn’t run the code:
const gen = simpleGen();
console.log(gen);
// [ Object [Generator] {} ]
The body runs lazily — only when you call .next():
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 } ]
Each .next() runs the generator up to the next yield and returns { value, done }.
yield — pause and produce a value:
yield does two things:
- Returns a value to the caller
- Pauses execution at that point
Execution resumes when .next() is called again — from the same line.
Generators are iterable:
for (const value of simpleGen()) {
console.log(value);
}
// [ 1 ]
// [ 2 ]
// [ 3 ]
for...of calls .next() until done: true.
Spread works:
console.log([...simpleGen()]);
// [ [ 1, 2, 3 ] ]
The generator returns an iterator:
const gen = simpleGen();
console.log(typeof gen.next);
// [ 'function' ]
console.log(typeof gen[Symbol.iterator]);
// [ 'function' ]
console.log(gen[Symbol.iterator]() === gen);
// [ true ]
This is what makes for...of and spread work — the generator itself is the iterator.
Function declaration vs expression:
// Declaration
function* genA() { yield 1; }
// Expression
const genB = function* () { yield 1; };
// Arrow — not possible
// const genC = *() => { yield 1; }; ❌
// Method
const obj = {
*genC() { yield 1; }
};
// Class method
class C {
*genD() { yield 1; }
}
Why generators matter:
| Use case | Why |
|---|---|
| Lazy sequences | Produce values on demand |
| Infinite sequences | Never compute more than needed |
| Two-way communication | Send values back into the generator |
| Custom iterators | Cleaner than manual [Symbol.iterator] |
| Async control flow | async function* for streams |
| Coroutines | Pause and resume execution |
| Memory efficiency | Don’t hold entire sequence in memory |
Generator vs regular function:
| Feature | Regular function | Generator |
|---|---|---|
| Executes on call | ✅ Immediately | ❌ On first .next() |
| Can pause | ❌ | ✅ at yield |
| Returns | Return value | Iterator |
| Multiple returns | One | Many (yield) |
| Preserves state | No | Yes |
| Syntax | function | function* |
The mental model:
┌──────────────────────────────────────────────┐
│ Regular function │
│ │
│ call → runs to completion → returns │
│ │
│ start ──► ... ──► return │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Generator │
│ │
│ call → returns iterator │
│ │
│ .next() → run to yield → pause │
│ .next() → resume → run to yield → pause │
│ .next() → resume → run to end → done │
│ │
│ start ──► yield ──► pause │
│ │ │
│ ▼ │
│ resume ──► yield ──► pause │
│ │ │
│ ▼ │
│ resume ──► end │
│ │
└──────────────────────────────────────────────┘
A practical example — a range generator:
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range(1, 5)]);
// [ [ 1, 2, 3, 4, 5 ] ]
for (const n of range(10, 15)) {
console.log(n);
}
// [ 10 ]
// [ 11 ]
// [ 12 ]
// [ 13 ]
// [ 14 ]
// [ 15 ]
An infinite Fibonacci:
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
The generator produces values forever — but you only compute what you ask for.
b – Generator methods and two-way communication
Generators aren’t just producers — they’re two-way channels. You can send values back into them.
The three methods on a generator object:
| Method | Purpose |
|---|---|
.next(value) | Resume, optionally sending a value |
.return(value) | Terminate early with a value |
.throw(error) | Throw an error into the generator |
.next(value) — send a value in:
The value passed to .next() becomes the return value of the yield expression:
function* twoWay() {
const name = yield 'What is your name?';
const age = yield `Hello, ${name}! How old are you?`;
yield `${name} is ${age} years old.`;
}
const dialog = twoWay();
console.log(dialog.next().value);
// [ 'What is your name?' ]
console.log(dialog.next('Alice').value);
// [ 'Hello, Alice! How old are you?' ]
console.log(dialog.next(30).value);
// [ 'Alice is 30 years old.' ]
The first .next() starts the generator and pauses at the first yield. Its argument is ignored (nothing to receive it). Subsequent calls deliver their argument as the result of the previous yield.
.return(value) — terminate early:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const it = gen();
console.log(it.next());
// [ { value: 1, done: false } ]
console.log(it.return('stopped'));
// [ { value: 'stopped', done: true } ]
console.log(it.next());
// [ { value: undefined, done: true } ]
return jumps out of the generator, running any finally blocks:
function* gen() {
try {
yield 1;
yield 2;
} finally {
console.log('cleanup');
}
}
const it = gen();
it.next();
it.return();
// [ 'cleanup' ]
.throw(error) — throw inside the generator:
function* gen() {
try {
yield 1;
yield 2;
} catch (err) {
console.log('Caught:', err.message);
yield 'recovered';
}
}
const it = gen();
console.log(it.next());
// [ { value: 1, done: false } ]
console.log(it.throw(new Error('boom')));
// [ 'Caught: boom' ]
// [ { value: 'recovered', done: false } ]
console.log(it.next());
// [ { value: undefined, done: true } ]
throw raises an exception at the current yield inside the generator, as if it happened there.
yield* — delegate to another iterable:
function* delegate() {
yield 1;
yield* [2, 3, 4];
yield 5;
}
console.log([...delegate()]);
// [ [ 1, 2, 3, 4, 5 ] ]
yield* yields everything from another iterable — array, string, another generator, or any iterable.
Delegating to another generator:
function* inner() {
yield 'a';
yield 'b';
}
function* outer() {
yield 'start';
yield* inner();
yield 'end';
}
console.log([...outer()]);
// [ [ 'start', 'a', 'b', 'end' ] ]
Return value from yield*:
yield* evaluates to the return value of the delegated generator:
function* inner() {
yield 1;
return 'inner-done';
}
function* outer() {
const result = yield* inner();
console.log('Inner returned:', result);
yield 2;
}
console.log([...outer()]);
// [ 'Inner returned: inner-done' ]
// [ [ 1, 2 ] ]
State preservation:
Between .next() calls, every local variable persists:
function* counter() {
let count = 0;
while (true) {
const cmd = yield count++;
if (cmd === 'reset') count = 0;
}
}
const c = counter();
console.log(c.next().value); // 0
console.log(c.next().value); // 1
console.log(c.next().value); // 2
console.log(c.next('reset').value); // 0
console.log(c.next().value); // 1
count survives across calls — the generator’s scope is captured.
Generator delegation with early return:
function* inner() {
try {
yield 1;
yield 2;
} finally {
console.log('inner cleanup');
}
}
function* outer() {
yield* inner();
yield 3;
}
const it = outer();
it.next();
it.return(); // [ 'inner cleanup' ]
return propagates through yield* to inner generators.
c – Common generator patterns
Generators shine in a handful of recurring patterns. These are the ones you’ll see in real code.
Pattern 1 — Lazy iteration with for...of:
function* evenNumbers(limit) {
for (let i = 0; i <= limit; i += 2) {
yield i;
}
}
for (const n of evenNumbers(10)) {
console.log(n);
}
// [ 0 ]
// [ 2 ]
// [ 4 ]
// [ 6 ]
// [ 8 ]
// [ 10 ]
Pattern 2 — Infinite sequences with .next():
function* naturals() {
let i = 0;
while (true) {
yield i++;
}
}
const nat = naturals();
nat.next().value; // 0
nat.next().value; // 1
nat.next().value; // 2
Pattern 3 — Taking N from a generator:
function* take(n, iterable) {
let i = 0;
for (const x of iterable) {
if (i++ >= n) return;
yield x;
}
}
console.log([...take(5, naturals())]);
// [ [ 0, 1, 2, 3, 4 ] ]
Pattern 4 — map and filter on generators:
function* map(fn, iterable) {
for (const x of iterable) {
yield fn(x);
}
}
function* filter(pred, iterable) {
for (const x of iterable) {
if (pred(x)) yield x;
}
}
console.log([...map(x => x * 2, [1, 2, 3])]);
// [ [ 2, 4, 6 ] ]
console.log([...filter(x => x > 2, [1, 2, 3, 4])]);
// [ [ 3, 4 ] ]
Pattern 5 — Async generators (async function*):
async function* asyncRange(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(r => setTimeout(r, 100));
yield i;
}
}
async function run() {
for await (const n of asyncRange(1, 3)) {
console.log(n);
}
}
run();
// [ 1 ]
// [ 2 ]
// [ 3 ]
Pattern 6 — Async generator for paginated APIs:
async function* fetchPages(url) {
let nextUrl = url;
while (nextUrl) {
const res = await fetch(nextUrl);
const data = await res.json();
for (const item of data.items) {
yield item;
}
nextUrl = data.next;
}
}
async function process() {
for await (const item of fetchPages('/api/items')) {
console.log(item);
}
}
Pattern 7 — Tree traversal:
function* walk(node) {
yield node.value;
for (const child of node.children) {
yield* walk(child);
}
}
const tree = {
value: 1,
children: [
{ value: 2, children: [] },
{ value: 3, children: [
{ value: 4, children: [] }
]}
]
};
console.log([...walk(tree)]);
// [ [ 1, 2, 3, 4 ] ]
Pattern 8 — Custom iterator without boilerplate:
class LinkedList {
constructor() {
this.head = null;
}
add(value) {
this.head = { value, next: this.head };
}
*[Symbol.iterator]() {
let node = this.head;
while (node) {
yield node.value;
node = node.next;
}
}
}
const list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
console.log([...list]);
// [ [ 3, 2, 1 ] ]
Using a generator method for [Symbol.iterator] is much cleaner than a manual iterator object.
Pattern 9 — Chunking an array:
function* chunk(arr, size) {
for (let i = 0; i < arr.length; i += size) {
yield arr.slice(i, i + size);
}
}
console.log([...chunk([1, 2, 3, 4, 5, 6, 7], 3)]);
// [ [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ] ]
Pattern 10 — IDs generator:
function* idGenerator(prefix = 'id') {
let n = 0;
while (true) {
yield `${prefix}-${++n}`;
}
}
const ids = idGenerator('user');
console.log(ids.next().value); // 'user-1'
console.log(ids.next().value); // 'user-2'
console.log(ids.next().value); // 'user-3'
Pattern 11 — Coroutine-style two-way communication:
function* player() {
let health = 100;
while (true) {
const action = yield { health };
if (action === 'damage') health -= 10;
if (action === 'heal') health += 5;
}
}
const game = player();
game.next(); // start
console.log(game.next('damage').value); // { health: 90 }
console.log(game.next('damage').value); // { health: 80 }
console.log(game.next('heal').value); // { health: 85 }
Pattern 12 — yield* for composition:
function* first() { yield 1; yield 2; }
function* second() { yield 3; yield 4; }
function* combined() {
yield* first();
yield* second();
}
console.log([...combined()]);
// [ [ 1, 2, 3, 4 ] ]
Pattern 13 — Reading lines from a stream (Node.js):
async function* readLines(stream) {
let buffer = '';
for await (const chunk of stream) {
buffer += chunk;
let lineEnd;
while ((lineEnd = buffer.indexOf('\n')) !== -1) {
yield buffer.slice(0, lineEnd);
buffer = buffer.slice(lineEnd + 1);
}
}
if (buffer) yield buffer;
}
Pattern 14 — Cycle through values:
function* cycle(arr) {
let i = 0;
while (true) {
yield arr[i];
i = (i + 1) % arr.length;
}
}
const colors = cycle(['red', 'green', 'blue']);
console.log(colors.next().value); // red
console.log(colors.next().value); // green
console.log(colors.next().value); // blue
console.log(colors.next().value); // red
Pattern 15 — Range with step:
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
console.log([...range(0, 10, 2)]);
// [ [ 0, 2, 4, 6, 8, 10 ] ]
Generators vs arrays:
| Aspect | Generator | Array |
|---|---|---|
| Memory | O(1) | O(n) |
| Infinite | ✅ | ❌ |
| Lazy | ✅ | ❌ |
| Indexable | ❌ | ✅ |
| Reusable | ❌ (one-shot) | ✅ |
.map / .filter | ❌ (but easy to add) | ✅ |
for...of | ✅ | ✅ |
| Spread | ✅ | ✅ |
Generators are one-shot:
const gen = simpleGen();
[...gen]; // [1, 2, 3]
[...gen]; // [] ← exhausted
Once exhausted, a generator yields nothing. Re-call the generator function to get a fresh iterator.
Common patterns summary:
| Pattern | Purpose |
|---|---|
range(start, end) | Numeric sequence |
naturals() | Infinite counter |
take(n, iter) | First n values |
map(fn, iter) | Transform lazily |
filter(pred, iter) | Select lazily |
chunk(arr, n) | Split into groups |
cycle(arr) | Repeat forever |
walk(tree) | Recursive traversal |
idGenerator() | Unique IDs |
async function* | Async streams |
Complete Example Session
// ============================================
// PART 1: BASIC GENERATOR
// ============================================
function* simpleGen() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGen();
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 } ]
// ============================================
// PART 2: FOR...OF
// ============================================
for (const value of simpleGen()) {
console.log(value);
}
// [ 1 ]
// [ 2 ]
// [ 3 ]
// ============================================
// PART 3: SPREAD
// ============================================
console.log([...simpleGen()]);
// [ [ 1, 2, 3 ] ]
// ============================================
// PART 4: GENERATOR IS ITERATOR
// ============================================
const it = simpleGen();
console.log(it[Symbol.iterator]() === it);
// [ true ]
// ============================================
// PART 5: RANGE
// ============================================
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range(1, 5)]);
// [ [ 1, 2, 3, 4, 5 ] ]
// ============================================
// PART 6: INFINITE FIBONACCI
// ============================================
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
// ============================================
// PART 7: TWO-WAY COMMUNICATION
// ============================================
function* twoWay() {
const name = yield 'What is your name?';
const age = yield `Hello, ${name}! How old are you?`;
yield `${name} is ${age} years old.`;
}
const dialog = twoWay();
console.log(dialog.next().value);
// [ 'What is your name?' ]
console.log(dialog.next('Alice').value);
// [ 'Hello, Alice! How old are you?' ]
console.log(dialog.next(30).value);
// [ 'Alice is 30 years old.' ]
// ============================================
// PART 8: RETURN
// ============================================
const g1 = simpleGen();
console.log(g1.next().value);
// [ 1 ]
console.log(g1.return('stopped'));
// [ { value: 'stopped', done: true } ]
console.log(g1.next());
// [ { value: undefined, done: true } ]
// ============================================
// PART 9: THROW
// ============================================
function* gen2() {
try {
yield 1;
yield 2;
} catch (err) {
console.log('Caught:', err.message);
yield 'recovered';
}
}
const g2 = gen2();
g2.next();
console.log(g2.throw(new Error('boom')));
// [ 'Caught: boom' ]
// [ { value: 'recovered', done: false } ]
// ============================================
// PART 10: YIELD*
// ============================================
function* delegate() {
yield 1;
yield* [2, 3];
yield 4;
}
console.log([...delegate()]);
// [ [ 1, 2, 3, 4 ] ]
// ============================================
// PART 11: NESTED GENERATORS
// ============================================
function* inner() {
yield 'a';
yield 'b';
}
function* outer() {
yield 'start';
yield* inner();
yield 'end';
}
console.log([...outer()]);
// [ [ 'start', 'a', 'b', 'end' ] ]
// ============================================
// PART 12: TAKE
// ============================================
function* naturals() {
let i = 0;
while (true) yield i++;
}
function* take(n, iterable) {
let i = 0;
for (const x of iterable) {
if (i++ >= n) return;
yield x;
}
}
console.log([...take(5, naturals())]);
// [ [ 0, 1, 2, 3, 4 ] ]
// ============================================
// PART 13: MAP / FILTER
// ============================================
function* map(fn, iterable) {
for (const x of iterable) yield fn(x);
}
function* filter(pred, iterable) {
for (const x of iterable) if (pred(x)) yield x;
}
console.log([...map(x => x * 2, [1, 2, 3])]);
// [ [ 2, 4, 6 ] ]
console.log([...filter(x => x > 2, [1, 2, 3, 4])]);
// [ [ 3, 4 ] ]
// ============================================
// PART 14: TREE TRAVERSAL
// ============================================
function* walk(node) {
yield node.value;
for (const child of node.children) {
yield* walk(child);
}
}
const tree = {
value: 1,
children: [
{ value: 2, children: [] },
{ value: 3, children: [{ value: 4, children: [] }] }
]
};
console.log([...walk(tree)]);
// [ [ 1, 2, 3, 4 ] ]
// ============================================
// PART 15: CUSTOM ITERATOR
// ============================================
class LinkedList {
constructor() { this.head = null; }
add(v) { this.head = { value: v, next: this.head }; }
*[Symbol.iterator]() {
let node = this.head;
while (node) {
yield node.value;
node = node.next;
}
}
}
const list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
console.log([...list]);
// [ [ 3, 2, 1 ] ]
// ============================================
// PART 16: ID GENERATOR
// ============================================
function* idGenerator(prefix = 'id') {
let n = 0;
while (true) yield `${prefix}-${++n}`;
}
const ids = idGenerator('user');
console.log(ids.next().value); // 'user-1'
console.log(ids.next().value); // 'user-2'
// ============================================
// PART 17: CHUNK
// ============================================
function* chunk(arr, size) {
for (let i = 0; i < arr.length; i += size) {
yield arr.slice(i, i + size);
}
}
console.log([...chunk([1, 2, 3, 4, 5, 6, 7], 3)]);
// [ [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ] ]
// ============================================
// PART 18: CYCLE
// ============================================
function* cycle(arr) {
let i = 0;
while (true) {
yield arr[i];
i = (i + 1) % arr.length;
}
}
const colors = cycle(['red', 'green', 'blue']);
console.log(colors.next().value); // red
console.log(colors.next().value); // green
console.log(colors.next().value); // blue
console.log(colors.next().value); // red
// ============================================
// PART 19: STATE PRESERVATION
// ============================================
function* counter() {
let count = 0;
while (true) {
const cmd = yield count++;
if (cmd === 'reset') count = 0;
}
}
const c = counter();
console.log(c.next().value); // 0
console.log(c.next().value); // 1
console.log(c.next('reset').value); // 0
// ============================================
// PART 20: FULL SCRIPT
// ============================================
function* simpleGen53() {
yield 1;
yield 2;
yield 3;
}
const gen53 = simpleGen53();
console.log(gen53.next());
console.log(gen53.next());
console.log(gen53.next());
console.log(gen53.next());
for (const value of simpleGen53()) {
console.log(value);
}
console.log([...simpleGen53()]);
function* range53(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range53(1, 5)]);
function* fibonacci53() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib53 = fibonacci53();
console.log(fib53.next().value);
console.log(fib53.next().value);
console.log(fib53.next().value);
console.log(fib53.next().value);
console.log(fib53.next().value);
function* twoWay53() {
const name = yield 'What is your name?';
const age = yield `Hello, ${name}! How old are you?`;
yield `${name} is ${age} years old.`;
}
const dialog53 = twoWay53();
console.log(dialog53.next().value);
console.log(dialog53.next('Alice').value);
console.log(dialog53.next(30).value);
function* delegate53() {
yield 1;
yield* [2, 3];
yield 4;
}
console.log([...delegate53()]);
Quick Reference
Generator Syntax
| Form | Example |
|---|---|
| Declaration | function* gen() {} |
| Expression | const gen = function* () {} |
| Method | { *gen() {} } |
| Class method | *[Symbol.iterator]() {} |
| Async | async function* gen() {} |
| Yield | yield value |
| Delegate | yield* iterable |
Generator Methods
| Method | Purpose |
|---|---|
.next(value) | Resume, send value |
.return(value) | Terminate |
.throw(err) | Throw in generator |
[Symbol.iterator]() | Returns self |
Return Object
| Field | Meaning |
|---|---|
value | Yielded or returned value |
done | true when finished |
yield Behavior
| Expression | Result |
|---|---|
yield x | Returns x to caller, pauses |
const v = yield x | v is next .next(v) argument |
yield* iter | Yields all from iter |
return x | Ends generator, value = x |
Common Patterns
| Pattern | Code |
|---|---|
| Range | function* range(a, b) { for (let i = a; i <= b; i++) yield i; } |
| Infinite | while (true) yield i++ |
| Take | for (const x of it) { if (i++ >= n) return; yield x; } |
| Map | for (const x of it) yield fn(x) |
| Filter | for (const x of it) if (pred(x)) yield x |
| Chain | yield* inner() |
| Async | async function* gen() { yield await p; } |
Generator vs Iterator
| Aspect | Generator | Manual Iterator |
|---|---|---|
| Syntax | function* | Object with next |
| State | Automatic | Manual |
yield | ✅ | ❌ |
| Complexity | Low | High |
| Best for | Most cases | Rare low-level control |
Generators vs Arrays
| Feature | Generator | Array |
|---|---|---|
| Memory | O(1) | O(n) |
| Infinite | ✅ | ❌ |
| Lazy | ✅ | ❌ |
| Indexable | ❌ | ✅ |
| Reusable | ❌ | ✅ |
| Chainable | Manual | Native |
Best Practices
✅ Do This:
// Use generators for lazy sequences
function* range(n) { for (let i = 0; i < n; i++) yield i; } // ✅
// Use yield* to delegate
function* all() { yield* a(); yield* b(); } // ✅
// Use for...of to iterate
for (const x of gen()) { ... } // ✅
// Use generators for Symbol.iterator
*[Symbol.iterator]() { for (const x of this.items) yield x; } // ✅
// Use async generators for streams
async function* fetchPages(url) { ... } // ✅
// Return early to stop
function* take(n, it) {
let i = 0;
for (const x of it) { if (i++ >= n) return; yield x; }
} // ✅
// Use try/finally for cleanup
function* gen() {
try { yield 1; } finally { cleanup(); }
} // ✅
// Chain lazily
[...take(5, map(x => x * 2, naturals()))] // ✅
❌ Don’t Do This:
// Don't expect generators to be reusable
const g = gen();
[...g]; // [1, 2, 3]
[...g]; // [] ❌ exhausted
// Don't forget the * for generator declarations
function gen() { yield 1; } // ❌ SyntaxError
function* gen() { yield 1; } // ✅
// Don't use arrow functions for generators
const gen = *() => { yield 1; }; // ❌ SyntaxError
// Don't yield inside nested regular functions
function* gen() {
[1, 2].forEach(x => yield x); // ❌ SyntaxError
}
function* gen() {
for (const x of [1, 2]) yield x; // ✅
}
// Don't forget yield* for delegation
function* gen() {
yield inner(); // ⚠️ yields the generator object, not its values
yield* inner(); // ✅
}
// Don't assume .return() skips finally
function* gen() {
try { yield 1; } finally { console.log('runs'); } // ⚠️ finally runs
}
// Don't use generators when an array is fine
function* small() { yield 1; yield 2; } // ⚠️ overkill
const small = [1, 2]; // ✅
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
Forgot * | SyntaxError | function* |
| Arrow generator | SyntaxError | Use function* |
| Reuse exhausted | Empty result | Re-call generator |
Forgot yield* | Yields generator object | Add * |
yield in callback | SyntaxError | Use for...of |
.next() arg on first call | Ignored | Send from second |
Expecting .length | Generators have none | Iterate |
[...gen] twice | Second empty | Re-create |
Real-World Examples
1. Basic Generator
function* gen() {
yield 1;
yield 2;
yield 3;
}
console.log([...gen()]);
// [ [ 1, 2, 3 ] ]
2. Step Through
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: undefined, done: true }
3. For…of
for (const x of gen()) {
console.log(x);
}
// [ 1 ]
// [ 2 ]
// [ 3 ]
4. Range
function* range(a, b) {
for (let i = a; i <= b; i++) yield i;
}
console.log([...range(1, 5)]);
// [ [ 1, 2, 3, 4, 5 ] ]
5. Infinite
function* naturals() {
let i = 0;
while (true) yield i++;
}
const n = naturals();
n.next().value; // 0
n.next().value; // 1
6. Take
function* take(k, it) {
let i = 0;
for (const x of it) {
if (i++ >= k) return;
yield x;
}
}
console.log([...take(3, naturals())]);
// [ [ 0, 1, 2 ] ]
7. Fibonacci
function* fib() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const f = fib();
[f.next().value, f.next().value, f.next().value, f.next().value];
// [ 0, 1, 1, 2 ]
8. Two-Way
function* twoWay() {
const name = yield 'Name?';
yield `Hi, ${name}`;
}
const g = twoWay();
g.next().value; // 'Name?'
g.next('Alice').value; // 'Hi, Alice'
9. Return Early
const g = gen();
g.next(); // 1
console.log(g.return('x')); // { value: 'x', done: true }
10. Throw
function* safe() {
try {
yield 1;
} catch (e) {
yield 'caught';
}
}
const g = safe();
g.next();
console.log(g.throw(new Error('x')).value);
// [ 'caught' ]
11. Yield*
function* all() {
yield* [1, 2];
yield* [3, 4];
}
console.log([...all()]);
// [ [ 1, 2, 3, 4 ] ]
12. Nested
function* outer() {
yield 'start';
yield* inner();
yield 'end';
}
function* inner() {
yield 'a';
yield 'b';
}
console.log([...outer()]);
// [ [ 'start', 'a', 'b', 'end' ] ]
13. Custom Iterator
class LinkedList {
constructor() { this.head = null; }
add(v) { this.head = { v, next: this.head }; }
*[Symbol.iterator]() {
let n = this.head;
while (n) {
yield n.v;
n = n.next;
}
}
}
14. ID Generator
function* ids(prefix = 'id') {
let n = 0;
while (true) yield `${prefix}-${++n}`;
}
const id = ids('user');
id.next().value; // 'user-1'
id.next().value; // 'user-2'
15. Map / Filter
function* map(fn, it) {
for (const x of it) yield fn(x);
}
function* filter(pred, it) {
for (const x of it) if (pred(x)) yield x;
}
[...map(x => x * 2, [1, 2, 3])]; // [2, 4, 6]
[...filter(x => x > 1, [1, 2, 3, 4])]; // [2, 3, 4]
16. Tree Walk
function* walk(n) {
yield n.value;
for (const c of n.children) yield* walk(c);
}
walk({ value: 1, children: [
{ value: 2, children: [] },
{ value: 3, children: [{ value: 4, children: [] }] }
]});
// [1, 2, 3, 4]
17. Chunk
function* chunk(arr, n) {
for (let i = 0; i < arr.length; i += n) {
yield arr.slice(i, i + n);
}
}
[...chunk([1, 2, 3, 4, 5], 2)];
// [[1, 2], [3, 4], [5]]
18. Cycle
function* cycle(arr) {
let i = 0;
while (true) {
yield arr[i];
i = (i + 1) % arr.length;
}
}
const c = cycle(['a', 'b', 'c']);
c.next().value; // a
c.next().value; // b
c.next().value; // c
c.next().value; // a
19. State Preservation
function* counter() {
let count = 0;
while (true) {
const cmd = yield count++;
if (cmd === 'reset') count = 0;
}
}
const c = counter();
c.next().value; // 0
c.next().value; // 1
c.next('reset').value; // 0
20. Full Script
function* simpleGen53() {
yield 1;
yield 2;
yield 3;
}
const gen53 = simpleGen53();
console.log(gen53.next());
console.log(gen53.next());
console.log(gen53.next());
console.log(gen53.next());
for (const value of simpleGen53()) {
console.log(value);
}
console.log([...simpleGen53()]);
function* range53(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range53(1, 5)]);
function* fibonacci53() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib53 = fibonacci53();
console.log(fib53.next().value);
console.log(fib53.next().value);
console.log(fib53.next().value);
console.log(fib53.next().value);
console.log(fib53.next().value);
function* twoWay53() {
const name = yield 'What is your name?';
const age = yield `Hello, ${name}! How old are you?`;
yield `${name} is ${age} years old.`;
}
const dialog53 = twoWay53();
console.log(dialog53.next().value);
console.log(dialog53.next('Alice').value);
console.log(dialog53.next(30).value);
function* delegate53() {
yield 1;
yield* [2, 3];
yield 4;
}
console.log([...delegate53()]);
Visual: Generator Lifecycle
┌──────────────────────────────────────────────┐
│ function* gen() { │
│ console.log('start'); │
│ yield 1; │
│ console.log('middle'); │
│ yield 2; │
│ console.log('end'); │
│ } │
│ │
│ const g = gen(); ← nothing printed │
│ │
│ g.next() │
│ prints 'start' │
│ returns { value: 1, done: false } │
│ │
│ g.next() │
│ prints 'middle' │
│ returns { value: 2, done: false } │
│ │
│ g.next() │
│ prints 'end' │
│ returns { value: undefined, done: true } │
│ │
└──────────────────────────────────────────────┘
Visual: Two-Way Communication
┌──────────────────────────────────────────────┐
│ Caller Generator │
│ │
│ .next() ──────────► runs to yield 1 │
│ ◄────────── yields 'Name?' │
│ │
│ .next('Alice') ───► name = 'Alice' │
│ runs to next yield │
│ ◄────────── yields 'Hi, Alice!' │
│ │
│ .next() ──────────► runs to end │
│ ◄────────── done: true │
│ │
│ Values flow BOTH ways │
│ │
└──────────────────────────────────────────────┘
Visual: yield* Delegation
┌──────────────────────────────────────────────┐
│ function* outer() { │
│ yield 1; │
│ yield* inner(); ← delegate │
│ yield 4; │
│ } │
│ │
│ function* inner() { │
│ yield 2; │
│ yield 3; │
│ } │
│ │
│ [...outer()] │
│ → [1, 2, 3, 4] │
│ │
│ yield* expands the inner iterable │
│ │
└──────────────────────────────────────────────┘
Visual: Generator vs Array
┌──────────────────────────────────────────────┐
│ Array [1..1_000_000] │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ All 1M values in memory │ │
│ └────────────────────────────────────────┘ │
│ │
│ Immediate, but heavy │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Generator range(1, 1_000_000) │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Current value: 1 │ │
│ │ (next value computed on demand) │ │
│ └────────────────────────────────────────┘ │
│ │
│ Lazy, light — O(1) memory │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Syntax | Example |
|---|---|---|
| Generator | function* gen() {} | Declares generator |
| Yield | yield value | Pause and produce |
| Delegate | yield* iter | Yield all from iter |
| Next | gen.next(v) | Resume, send v |
| Return | gen.return(v) | Terminate early |
| Throw | gen.throw(e) | Throw inside |
| Iterate | for (const x of gen()) | Standard loop |
| Spread | [...gen()] | Collect all |
| Async | async function* gen() | Async yields |
| Custom iterator | *[Symbol.iterator]() | Generator method |
| Two-way | const v = yield x | Receive from caller |
| State | Preserved between yields | Automatic |
| Done | { value, done: true } | End of generator |
| Exhausted | One-shot | Can’t reuse |
Key takeaways:
- Generators are functions that pause and resume at
yield - Declared with
function*— the asterisk is required - Calling a generator returns an iterator — the body doesn’t run yet
yieldproduces a value and pauses;.next()resumes- Generators are iterable — use
for...of, spread, destructuring yield*delegates to another iterable — arrays, generators, anything iterable.next(value)sends a value into the generator at the pausedyield.return(v)terminates early;.throw(e)raises an error inside- State is preserved between calls — local variables survive
- Generators are one-shot — once exhausted, they’re empty
- Use generators for lazy sequences, infinite streams, tree traversal, custom iterators, and async streams
async function*combines generators withawaitforfor await...of
Remember: Generators turn functions into resumable processes. Declare with function*, pause with yield, drive with .next(), iterate with for...of. Use yield* to compose, .return() to stop, .throw() to inject errors. Generators give you lazy evaluation, two-way communication, and clean custom iterators — all with less code than manual state machines. Master generators, and your sequences become infinite and your iteration becomes elegant.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!