JavaScript Iterators

Learn iterators, the iteration protocol, and generators for controlling how data is consumed.

TL;DR

  1. 01Iterators expose a next method returning value and done.
  2. 02Add Symbol.iterator to make any custom object iterable.
  3. 03Use generator functions to build iterators with less code.

Tips

  1. 01Reach for generators before writing manual iterators, since <code>function*</code> and <code>yield</code> handle state and the <code>next()</code> contract for you automatically.
  2. 02Use yield* to delegate to another iterable inside a generator, which avoids manually looping and re-yielding each value.

Warnings

  1. 01Iterators are one-time use — once <code>done</code> is <code>true</code>, create a new iterator to traverse the data again.
  2. 02Plain objects are not iterable by default, so for-of on one directly throws a TypeError unless you call Object.entries() first.

What Iterators Are

  • Every for-of loop, spread, and destructuring call secretly relies on one tiny contract: a next() method.
    const arr = [10, 20];
    const it = arr[Symbol.iterator]();
    it.next(); // { value: 10, done: false }
    
  • Each call to next() returns an object with value and done properties.
  • Built-in features like for...of, spread, and destructuring use iterators behind the scenes.
  • Iterators let you control how a collection is traversed, one item at a time.
  • When done becomes true, the iteration is finished and value is undefined.

Iteration Protocol

  • Iterable and iterator are different roles — an iterable's job is just to hand back an iterator on request.
    const obj = {
      data: [1, 2, 3],
      [Symbol.iterator]() {
        let i = 0;
        return {
          next: () => ({
            value: this.data[i],
            done: i++ >= this.data.length
          })
        };
      }
    };
    for (const n of obj) console.log(n);
    
  • This protocol lets you use your own objects in for...of loops.
  • The returned iterator must follow the same next() shape as built-in ones.
  • Custom iterables work everywhere standard iterables do, like spread syntax.
  • Most built-in objects already follow this protocol out of the box.

Built-in Iterables

  • Plain objects are the one common exception — arrays, strings, Maps, and Sets all ship iterable, objects don't.
    for (const char of "Hi!") console.log(char);
    
  • Use for...of to loop over any built-in iterable cleanly.
    const set = new Set([1, 2]);
    for (const v of set) console.log(v);
    
  • Maps yield [key, value] pairs, which you can destructure in the loop.
    const map = new Map([['a', 1]]);
    for (const [k, v] of map) console.log(k, v);
    
  • NodeLists from the DOM are also iterable using for...of.
  • Plain objects are not iterable by default — use Object.entries() first.

Spread and Custom Iterators

  • Spread syntax has no idea what a Set or Map is internally — it just calls Symbol.iterator like everything else.
    const unique = new Set([1, 2, 2]);
    console.log([...unique]); // [1, 2]
    
  • Build a manual iterator with full control over the sequence and end condition.
    function makeCounter(limit) {
      let count = 0;
      return {
        next() {
          if (count < limit) return { value: count++, done: false };
          return { done: true };
        }
      };
    }
    
  • Custom iterators are useful for generating infinite or computed sequences.
  • The spread operator is also great for cloning arrays and merging iterables.
  • Iterators stop being useful once exhausted, so create a new one if needed.

Generators

  • A generator can pause mid-function and resume later with a new value injected — no other JavaScript construct does that.
    function* greet() {
      const name = yield "What's your name?";
      yield `Hello, ${name}!`;
    }
    const g = greet();
    g.next().value; // "What's your name?"
    g.next("Ada").value; // "Hello, Ada!"
    
  • The yield keyword pauses execution and returns the next value.
  • Pass data back into the generator using next(value) for two-way communication.
  • Delegate to another iterable using yield* inside a generator function.
    function* combine() {
      yield* [1, 2];
      yield* ['a', 'b'];
    }
    console.log([...combine()]);
    
  • Generators are the easiest way to create custom iterators in modern JavaScript.

FAQ