Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 17

Iterators and Generators

This chapter assumes you are comfortable with closures, `this`, arrays, the spread operator, and async/await. Iterators and generators sit below the abstraction of `for...of` and above it: understanding them explains how iteration protocols work, why spread works on some values and not others, and h
Level: AdvancedLength: Mid2 min read

Read at another depth

Iterators and Generators

This chapter assumes you are comfortable with closures, this, arrays, the spread operator, and async/await. Iterators and generators sit below the abstraction of for...of and above it: understanding them explains how iteration protocols work, why spread works on some values and not others, and how lazy evaluation is implemented in JavaScript.

The iteration protocol

Two contracts define iteration:

  • Iterable: an object with a [Symbol.iterator] method returning an iterator.
  • Iterator: an object with a next() method returning { value, done }.

for...of, spread, destructuring, Array.from, and yield* all consume the protocol — not arrays specifically.

Code example in javascript
const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      },
    };
  },
};

console.log([...range]); // [1, 2, 3]

Key consequence: an iterator is single-use. Iterating range again calls [Symbol.iterator] again, producing a fresh iterator — but spreading the iterator itself ([...range[Symbol.iterator]()]) exhausts it permanently.

Generators

A generator function (function*) builds iterables without hand-writing the protocol. Calling it does not run the body; it returns a generator object that is both an iterable and an iterator.

Code example in javascript
function* fib() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const seq = fib();
console.log(seq.next().value); // 0
console.log(seq.next().value); // 1
console.log(seq.next().value); // 1

yield suspends the function mid-body and preserves its local state — this is a closure-like frame, but with resumable execution. yield* delegates to another iterable or generator, propagating return values.

Two-way communication

next(arg) resumes execution with arg as the value of the pending yield; generator.return(x) forces done at the current yield point; generator.throw(e) raises at it. This makes generators coroutines, not just lazy sequences.

Code example in javascript
function* echo() {
  const received = yield 'ready';
  yield `got: ${received}`;
}
const g = echo();
console.log(g.next().value);     // 'ready' (first next arg is ignored)
console.log(g.next(42).value);   // 'got: 42'

Laziness and composition

Generators never hold the whole sequence in memory; they compute on demand. Compose them with plain function pipelines instead of array methods to avoid intermediate allocations.

Code example in javascript
function* map(iter, fn) {
  for (const item of iter) yield fn(item);
}
function* take(iter, n) {
  let i = 0;
  for (const item of iter) {
    if (i++ >= n) return;
    yield item;
  }
}
const first5Even = [...take(map(fib(), (x) => x * 2), 5)];

Gotchas and decision criteria

  • No backpressure for free: an unthrottled infinite generator still runs as fast as its consumer pulls; the pull model is the backpressure.
  • Async generators (async function*) yield promises and pair with for await...of, ideal for paged API consumption — covered alongside async chapters.
  • Error handling is subtle: an exception thrown inside a generator during next() propagates to the caller; the generator is done.
  • Use generators when the data is unbounded, expensive, or streaming. Use plain arrays when you need random access, repeated iteration, or length. Reach for generators as eager factories (fib()) rather than exposing internal iterators.

Exercise

What happens if you spread an iterator twice?

Show answer

The second spread yields nothing. The first spread exhausts the iterator, and subsequent next() calls return { value: undefined, done: true }. Iterables stay fresh because [Symbol.iterator] is invoked again; bare iterators do not.

Exercise

How do you pass data into a generator?

Show answer

Pass it as the argument to next(): the value replaces the currently pending yield expression. The first next() call has no pending yield, so its argument is ignored.

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Iterators and Generators

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that