JavaScript Iterator Interface and next() Method

In JavaScript, the Iterator interface and the next() method provide a standardized mechanism for traversing sequences of data one element at a time. This article explores how the iteration protocol functions under the hood, the structure and role of the next() method, and how JavaScript utilizes these constructs to power modern language features like the for...of loop, destructuring, and the spread operator.

The Iteration Protocol

The JavaScript iteration protocol defines how any object can become iterable. It is split into two distinct protocols:

  1. The Iterable Protocol: An object must define a method at the key [Symbol.iterator]. This method is a factory that returns an Iterator object.
  2. The Iterator Protocol: An object must implement a standard interface containing a next() method.

The Role of the next() Method

The next() method is the core execution mechanism of an iterator. It acts as a cursor that moves through a data structure, maintaining internal state across invocations.

Every time next() is called, it must return an object with two specific properties:

Return Object Structure

{
  value: any,
  done: boolean
}

When done becomes true, subsequent calls to next() should continue returning { value: undefined, done: true }.

Implementing a Custom Iterator

To understand how the interface functions, consider a custom range iterator that generates numbers from a starting point to an endpoint:

function createRangeIterator(start = 0, end = Infinity, step = 1) {
  let nextIndex = start;
  let count = 0;

  return {
    next() {
      if (nextIndex <= end) {
        const result = { value: nextIndex, done: false };
        nextIndex += step;
        count++;
        return result;
      }
      return { value: count, done: true };
    }
  };
}

const range = createRangeIterator(1, 3);
console.log(range.next()); // { value: 1, done: false }
console.log(range.next()); // { value: 2, done: false }
console.log(range.next()); // { value: 3, done: false }
console.log(range.next()); // { value: 3, done: true }

Making an Object Iterable

To allow an object to be consumed by native syntax like for...of, the iterator must be attached to the Symbol.iterator property:

const collection = {
  items: ['alpha', 'beta', 'gamma'],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.items.length) {
          return { value: this.items[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const item of collection) {
  console.log(item); // Logs: 'alpha', 'beta', 'gamma'
}

Native Language Integration

The Iterator interface and next() method power multiple native JavaScript features: