PureDevTools

JavaScript Iterator & Generator Reference

Iterator protocol, generator functions, and async iterators — with syntax, parameters, edge cases, and a live code runner

All processing happens in your browser. No data is sent to any server.
APIPurposeES
Symbol.iteratorDefines the default iterator for an object.ES2015
for...ofLoops over any iterable object, calling [Symbol.ES2015
next()Advances the iterator by one step and returns { value, done }.ES2015
return()Terminates the iterator early and returns { value, done: true }.ES2015
throw()Injects an exception into the generator at the paused yield point.ES2015
Array.from()Creates a new array from any iterable or array-like object, exhausting the iterator completely.ES2015
spread (...)Expands an iterable's values into an array literal or function arguments.ES2015
Symbol.iteratorIterator ProtocolES2015
obj[Symbol.iterator]() { return iteratorObject; }

Defines the default iterator for an object. Any object that implements a [Symbol.iterator]() method returning an iterator is called an *iterable* — it can be used in for...of loops, spread expressions, destructuring assignments, and Array.from().

▼
for...ofIterator ProtocolES2015
for (const item of iterable) { ... }

Loops over any iterable object, calling [Symbol.iterator]() once and then next() on each iteration until done is true. Works with arrays, strings, Maps, Sets, generators, and any object implementing the iterable protocol.

▼
next()Iterator ProtocolES2015
iterator.next(value?)

Advances the iterator by one step and returns { value, done }. The optional value argument becomes the result of the yield expression inside a generator — enabling two-way communication. When done is true, the iteration is complete.

▼
return()Iterator ProtocolES2015
iterator.return(value?)

Terminates the iterator early and returns { value, done: true }. Inside generators it executes any pending finally blocks, allowing resource cleanup. Called automatically by for...of when the loop exits via break or return.

▼
throw()Iterator ProtocolES2015
iterator.throw(error)

Injects an exception into the generator at the paused yield point. If the generator has a try...catch around the paused yield, it can handle the error and continue. If not caught, the exception propagates to the throw() caller and the generator terminates.

▼
Array.from()Iterator ProtocolES2015
Array.from(iterable, mapFn?)

Creates a new array from any iterable or array-like object, exhausting the iterator completely. Accepts an optional mapping function applied to each element during conversion.

▼
spread (...)Iterator ProtocolES2015
[...iterable] / fn(...iterable)

Expands an iterable's values into an array literal or function arguments. Internally calls [Symbol.iterator]() and exhausts the entire iterator. Can be combined with other values in array literals.

▼

You’re paginating API results — each page is a fetch call, and you want to process items one at a time across pages without loading all 10,000 results into memory. You could write a recursive function with callbacks, or you could use an async generator: async function* fetchPages(url) that yields items one by one and for await...of consumes them. But the iterator protocol, Symbol.iterator vs Symbol.asyncIterator, yield vs yield*, and next()/return()/throw() methods are a lot to hold in your head.

Why This Reference (Not MDN)

MDN splits iterators, generators, async iterators, and async generators across 6+ pages. This reference puts the entire protocol on one page — Symbol.iterator, for...of, next()/return()/throw(), function*, yield, yield*, Symbol.asyncIterator, async function*, and for await...of — with interactive examples.

What Are JavaScript Iterators and Generators?

The iterator protocol and generator functions are JavaScript’s built-in mechanism for lazy, on-demand sequences. Instead of computing all values upfront and storing them in an array, iterators produce values one at a time — only when requested.

// The simplest custom iterator
const counter = {
  [Symbol.iterator]() {
    let n = 0;
    return {
      next() {
        return n < 3
          ? { value: n++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

for (const n of counter) console.log(n); // 0  1  2
console.log([...counter]);               // [0, 1, 2]

The Iterator Protocol

Symbol.iterator — Making Objects Iterable

Any object becomes iterable by implementing [Symbol.iterator](). This method is called once when the iterable enters a for...of loop or a spread expression, and must return an iterator.

class Range {
  constructor(from, to) { this.from = from; this.to = to; }
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
}

const r = new Range(1, 5);
console.log([...r]);              // [1, 2, 3, 4, 5]
console.log(Array.from(r));       // [1, 2, 3, 4, 5]
const [first, second] = r;
console.log(first, second);       // 1  2

for…of — Iterating Over Any Iterable

for...of works with all built-in iterables (Array, String, Map, Set, TypedArray, arguments, NodeList, generators) and any custom object implementing [Symbol.iterator]().

// Arrays
for (const n of [10, 20, 30]) console.log(n); // 10  20  30

// Strings (iterates Unicode code points, not bytes)
for (const ch of "hi 👋") console.log(ch);  // h  i  (space)  👋

// Maps (key-value pairs)
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, val] of map) console.log(key, "->", val); // a -> 1  b -> 2

// Sets (unique values)
for (const v of new Set([1, 2, 2, 3])) console.log(v);  // 1  2  3

next(), return(), throw() — The Iterator Interface

Every iterator exposes three methods. next() is required; return() and throw() are optional but standard:

MethodPurpose
next(value?)Advance one step; returns { value, done }
return(value?)Terminate early; triggers finally in generators
throw(error)Inject an exception at the paused yield
function* source() {
  try {
    yield 1;
    yield 2;
    yield 3;
  } finally {
    console.log("iterator closed");
  }
}

const it = source();
console.log(it.next());          // { value: 1, done: false }
console.log(it.return("done"));  // iterator closed  { value: "done", done: true }

Generator Functions

function* — Declaring a Generator

Generator functions use function* syntax. Calling them does NOT execute the body — it returns a Generator object. The body runs only when next() is called:

function* countdown(n) {
  while (n > 0) {
    yield n--;
  }
  return "blastoff!";
}

const gen = countdown(3);
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: "blastoff!", done: true }
console.log(gen.next()); // { value: undefined, done: true }

The generator’s return value appears in the last { done: true } result — but for...of and spread skip it. To capture it you must call next() manually.

yield — Two-Way Communication

yield both produces a value outward and receives a value inward. The value passed to next(value) becomes the result of the paused yield expression:

function* logger() {
  let count = 0;
  while (true) {
    const msg = yield count++;
    if (msg === "reset") count = 0;
    console.log("logged:", msg, "count now:", count);
  }
}

const log = logger();
log.next();           // Start the generator
log.next("a");        // logged: a  count now: 1
log.next("b");        // logged: b  count now: 2
log.next("reset");    // logged: reset  count now: 0

yield* — Delegating to Another Iterable

yield* flattens another iterable into the current generator’s output. It also collects the inner generator’s return value:

function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) yield* flatten(item); // recursive
    else yield item;
  }
}

console.log([...flatten([1, [2, [3, 4]], 5])]); // [1, 2, 3, 4, 5]

Common Generator Patterns

Infinite Sequence

function* naturals(start = 1) {
  while (true) yield start++;
}

function take(n, iter) {
  const result = [];
  for (const v of iter) {
    result.push(v);
    if (result.length === n) break;
  }
  return result;
}

console.log(take(5, naturals())); // [1, 2, 3, 4, 5]

Lazy Pipeline

function* map(iter, fn) {
  for (const v of iter) yield fn(v);
}

function* filter(iter, pred) {
  for (const v of iter) if (pred(v)) yield v;
}

function* take(n, iter) {
  for (const v of iter) {
    yield v;
    if (--n === 0) break;
  }
}

function* naturals() { let n = 0; while (true) yield n++; }

// Find the first 3 even squares — all lazily evaluated
const result = take(3, filter(map(naturals(), n => n * n), n => n % 2 === 0));
console.log([...result]); // [0, 4, 16]

State Machine

function* trafficLight() {
  while (true) {
    yield "green";
    yield "yellow";
    yield "red";
  }
}

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

Async Iterators and Generators

Symbol.asyncIterator — Async Iterable Protocol

Objects implement [Symbol.asyncIterator]() to become async iterables. The method returns an async iterator whose next() returns a Promise<{ value, done }>:

async function run() {
  const timedSequence = {
    [Symbol.asyncIterator]() {
      const items = [1, 2, 3];
      let i = 0;
      return {
        async next() {
          await new Promise(r => setTimeout(r, 50));
          return i < items.length
            ? { value: items[i++], done: false }
            : { value: undefined, done: true };
        }
      };
    }
  };

  for await (const n of timedSequence) {
    console.log(n); // 1  2  3  (each with 50ms delay)
  }
}
run();

async function* — Async Generator Functions

Async generators combine await and yield. They are ideal for paginated APIs, streaming responses, and any data that arrives asynchronously:

async function* fetchPages(url, maxPages) {
  let page = 1;
  while (page <= maxPages) {
    // In real code: const data = await fetch(`${url}?page=${page}`).then(r => r.json());
    await new Promise(r => setTimeout(r, 0)); // simulate network
    yield { page, data: ["item" + (page * 2 - 1), "item" + (page * 2)] };
    page++;
  }
}

async function run() {
  for await (const { page, data } of fetchPages("/api/items", 3)) {
    console.log("page", page, "->", data.join(", "));
  }
}

run();

for await…of — Async Iteration Loop

for await...of iterates async iterables, awaiting each item. It also works on sync iterables (wrapping each value with Promise.resolve):

async function* countdown(n) {
  while (n > 0) {
    await new Promise(r => setTimeout(r, 0));
    yield n--;
  }
}

async function run() {
  for await (const n of countdown(3)) {
    console.log(n); // 3  2  1
  }
}
run();

Built-In Iterables Cheat Sheet

ValueSymbol.iterator?Yields
ArrayYesElements
StringYesUnicode code points (not UTF-16 code units)
MapYes[key, value] pairs
SetYesValues (in insertion order)
TypedArrayYesElements
argumentsYesArgument values
NodeListYesDOM nodes
GeneratorYesYielded values (the generator IS its own iterator)
Plain {}No— (use Object.entries / Object.keys)

Frequently Asked Questions

What is the difference between an iterator and an iterable?

An iterator is an object with a next() method returning { value, done }. An iterable is an object with a [Symbol.iterator]() method that returns an iterator. These are separate protocols. Generators satisfy both: the generator object IS its own iterator (gen[Symbol.iterator]() === gen), so it is both an iterable and an iterator.

Can a generator be paused from outside?

No. Generators are cooperative — they pause themselves with yield. The caller cannot forcibly suspend a generator mid-execution. The only way to advance or interact with a generator is through its next(), return(), and throw() methods.

What happens to a generator after it returns?

After a generator’s function body completes (via return or falling off the end), the generator is exhausted. All subsequent next() calls return { value: undefined, done: true }. Calling return() on an exhausted generator is a no-op.

Why does for…of skip the generator’s return value?

The for...of loop stops when it sees { done: true } — and the value property of that last result is ignored. This is by design: the convention is that return values from generators are metadata, not part of the produced sequence. To capture a return value, call next() manually until done is true and read result.value.

How do I convert an async iterable to an array?

Array.from() and spread ([...]) do NOT work with async iterables — they only support sync iterables. To collect all values from an async iterable, use for await...of:

async function toArray(asyncIterable) {
  const result = [];
  for await (const item of asyncIterable) result.push(item);
  return result;
}

When should I use a generator vs a regular function returning an array?

Use a generator when: (1) the sequence is lazy — you want to avoid computing all values upfront; (2) the sequence is infinite; (3) you need early termination and want cleanup via finally; (4) you are building a pipeline of transformations. Use an array when the full result is needed immediately, the sequence is small and finite, or you need random access.

Related Tools

More JavaScript Tools