JavaScript Iterator Table

PieceProtocol roleField note
Symbol.iteratorThe iterable protocolObjects with this method work in for...of, spread, destructuring
next()The iterator protocolReturns {value, done} - the engine calls it repeatedly
obj[Symbol.iterator] = function*Make any object iterableCustom classes gain for...of support
yieldPause and produce a valueGenerator functions implement the iterator protocol for free
for...ofConsume any iterableArrays, strings, Maps, Sets, generators, NodeList
[...iterable]Spread any iterableConverts iterables to arrays - NodeList to array
Map / Set built-in iteratorsCollection iterationmap.entries() / set.values() return real iterators
Reference: the MDN iteration protocols reference. Two protocols power all iteration: the ITERABLE protocol (has Symbol.iterator returning an iterator) and the ITERATOR protocol (has next() returning {value, done}). Generators satisfy both automatically. The practical consequence: your custom classes gain for...of, spread, and destructuring for free by implementing one method. Bottom line: the protocol design means ANY object can participate - you don't subclass Array to make something iterable, you just add Symbol.iterator. Related tools: loops table (the consumption side), DOM traversal table (NodeList is array-like but not iterable pre-2020), and spread table for the expansion syntax.

Two protocols power all JavaScript iteration: the ITERABLE protocol (an object has Symbol.iterator returning an iterator) and the ITERATOR protocol (an object has next() returning {value, done}). Generators satisfy both automatically. The table below is the working seven - the pieces that make any custom class work in for...of, spread and destructuring.

Bottom line: implementing ONE method (Symbol.iterator) gives your custom class for...of, spread, destructuring and Array.from support for free. The protocol design means any object can participate in iteration without subclassing Array - you just add the method and the engine handles the rest.

The honest part: the two-protocol design is intentional decoupling. The iterable protocol says 'I can produce an iterator'; the iterator protocol says 'I can produce values on demand'. Separating them means the same iterator can serve multiple consumers, and a lazy generator can produce values only when pulled - the foundation for infinite sequences and streaming data.

How to use

  1. Add Symbol.iterator to any class: * [Symbol.iterator]() { yield ...; } makes it work in for...of immediately.
  2. Use yield inside generator functions - the generator implements the iterator protocol automatically.
  3. Spread any iterable into an array: [...myCustomIterable] for array methods access.

Frequently asked questions

What is the difference between an iterable and an iterator?

An iterable HAS a Symbol.iterator method that RETURNS an iterator; an iterator HAS a next() method that returns {value, done}. The iterable is the container ('I can give you an iterator'); the iterator is the cursor ('I can give you the next value'). Arrays are iterables - calling arr[Symbol.iterator]() returns a fresh iterator. The separation means multiple consumers can each get their own cursor over the same data.

How do I make a custom class iterable?

Add Symbol.iterator as a generator method: class Library { *[Symbol.iterator]() { for (const shelf of this.shelves) yield *shelf.books; } }. The * makes it a generator, and yield * delegates to inner iterables. Now for (const book of library), [...library] and destructuring all work. The generator's lazy evaluation means books are produced one at a time - no intermediate array needed.

What does done: true mean in the iterator protocol?

It signals exhaustion: {value: undefined, done: true} tells the consumer there are no more values. for...of stops on done: true automatically; spread stops too. The subtle part: you can omit value when done is true ({done: true} is valid), and the consumer just sees undefined. After done: true, subsequent next() calls keep returning {done: true} - the iterator is exhausted but not broken.

When should I write a custom iterator instead of a generator?

Almost never in modern code - generators are the ergonomic way to implement the iterator protocol. A generator function automatically returns an object with next(), done and the full iterator contract. Write a raw iterator (with explicit next()) only when you need fine-grained control over the iteration state machine, or when teaching the protocol. In production code, generator functions make custom iteration a one-method implementation.

Related tools