|

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:

  1. Returns a value to the caller
  2. 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 caseWhy
Lazy sequencesProduce values on demand
Infinite sequencesNever compute more than needed
Two-way communicationSend values back into the generator
Custom iteratorsCleaner than manual [Symbol.iterator]
Async control flowasync function* for streams
CoroutinesPause and resume execution
Memory efficiencyDon’t hold entire sequence in memory

Generator vs regular function:

FeatureRegular functionGenerator
Executes on call✅ Immediately❌ On first .next()
Can pause✅ at yield
ReturnsReturn valueIterator
Multiple returnsOneMany (yield)
Preserves stateNoYes
Syntaxfunctionfunction*

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:

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

AspectGeneratorArray
MemoryO(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:

PatternPurpose
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

FormExample
Declarationfunction* gen() {}
Expressionconst gen = function* () {}
Method{ *gen() {} }
Class method*[Symbol.iterator]() {}
Asyncasync function* gen() {}
Yieldyield value
Delegateyield* iterable

Generator Methods

MethodPurpose
.next(value)Resume, send value
.return(value)Terminate
.throw(err)Throw in generator
[Symbol.iterator]()Returns self

Return Object

FieldMeaning
valueYielded or returned value
donetrue when finished

yield Behavior

ExpressionResult
yield xReturns x to caller, pauses
const v = yield xv is next .next(v) argument
yield* iterYields all from iter
return xEnds generator, value = x

Common Patterns

PatternCode
Rangefunction* range(a, b) { for (let i = a; i <= b; i++) yield i; }
Infinitewhile (true) yield i++
Takefor (const x of it) { if (i++ >= n) return; yield x; }
Mapfor (const x of it) yield fn(x)
Filterfor (const x of it) if (pred(x)) yield x
Chainyield* inner()
Asyncasync function* gen() { yield await p; }

Generator vs Iterator

AspectGeneratorManual Iterator
Syntaxfunction*Object with next
StateAutomaticManual
yield
ComplexityLowHigh
Best forMost casesRare low-level control

Generators vs Arrays

FeatureGeneratorArray
MemoryO(1)O(n)
Infinite
Lazy
Indexable
Reusable
ChainableManualNative

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

PitfallProblemSolution
Forgot *SyntaxErrorfunction*
Arrow generatorSyntaxErrorUse function*
Reuse exhaustedEmpty resultRe-call generator
Forgot yield*Yields generator objectAdd *
yield in callbackSyntaxErrorUse for...of
.next() arg on first callIgnoredSend from second
Expecting .lengthGenerators have noneIterate
[...gen] twiceSecond emptyRe-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

ConceptSyntaxExample
Generatorfunction* gen() {}Declares generator
Yieldyield valuePause and produce
Delegateyield* iterYield all from iter
Nextgen.next(v)Resume, send v
Returngen.return(v)Terminate early
Throwgen.throw(e)Throw inside
Iteratefor (const x of gen())Standard loop
Spread[...gen()]Collect all
Asyncasync function* gen()Async yields
Custom iterator*[Symbol.iterator]()Generator method
Two-wayconst v = yield xReceive from caller
StatePreserved between yieldsAutomatic
Done{ value, done: true }End of generator
ExhaustedOne-shotCan’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
  • yield produces 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 paused yield
  • .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 with await for for 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!