JavaScript Iterators: Complete Guide

An iterator is any object that defines a next() method returning {value, done}. Learn how JavaScript iteration works under the hood, and how to build custom iterators for any data structure.

6 min read

JavaScript iterators are any object that has a next() method. Call it, and you get back an object with two properties: value (the current item) and done (a boolean that signals whether the sequence is finished).

That is the entire iterator protocol. It is one of the simplest and most powerful conventions in JavaScript:

javascriptjavascript
// The smallest possible iterator
const counter = {
  current: 1,
  next() {
    if (this.current > 3) {
      return { value: undefined, done: true };
    }
    return { value: this.current++, done: false };
  }
};
 
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: 3, done: false }
console.log(counter.next()); // { value: undefined, done: true }

Every time you call next(), the iterator advances and produces the next value. When the sequence is exhausted, it returns done set to true. The iterator itself holds the state of where it is in the sequence.

Iterator vs Iterable

These two terms are often confused:

Iterator and iterable relationship
IterableIterator
HasA Symbol.iterator methodA next() method
ProducesAn iterator, when its Symbol.iterator method is calledValue and done objects, one per call to next
ExampleAn arrayThe object returned by calling Symbol.iterator on an array

Arrays are iterables. They are not iterators. When you write a for...of loop, JavaScript calls the array's Symbol.iterator method to get a fresh iterator and then calls next on it repeatedly:

javascriptjavascript
const arr = ["a", "b", "c"];
 
const iterator = arr[Symbol.iterator]();
 
console.log(iterator.next()); // { value: "a", done: false }
console.log(iterator.next()); // { value: "b", done: false }
console.log(iterator.next()); // { value: "c", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

This is exactly what for...of does behind the scenes. The spread operator and Array.from() use the same protocol.

How for...of Uses Iterators

The for...of loop is syntactic sugar over the iterator protocol. This:

javascriptjavascript
for (const item of someIterable) {
  console.log(item);
}

Is equivalent to manually grabbing an iterator and looping until it reports that it is done, checking the done flag after every single call to next:

javascriptjavascript
const iterator = someIterable[Symbol.iterator]();
let result = iterator.next();
 
while (!result.done) {
  const item = result.value;
  console.log(item);
  result = iterator.next();
}

Understanding this demystifies why you can use for...of with arrays, strings, Maps, Sets, NodeLists, and even generators, but not with plain objects. There is nothing magical about it. It just follows the protocol.

Built-In Iterables

JavaScript comes with several built-in iterables. Each returns a different kind of iterator:

IterableIterator yields
ArrayEach element in order
StringEach Unicode code point
Mapkey/value pairs
SetEach value in insertion order
NodeListEach DOM node
argumentsEach function argument
TypedArrayEach numeric element
javascriptjavascript
// Map iteration yields [key, value] pairs
const scores = new Map([
  ["Alice", 95],
  ["Bob", 82]
]);
 
for (const [name, score] of scores) {
  console.log(`${name}: ${score}`);
}
// Alice: 95
// Bob: 82

Map iterators produce key/value arrays, which is why destructuring in the for...of header works naturally.

Building a Custom Iterator

Any object can be an iterator. Here is one that produces the Fibonacci sequence:

javascriptjavascript
function createFibonacciIterator(limit) {
  let a = 0, b = 1, count = 0;
 
  return {
    next() {
      if (count >= limit) {
        return { value: undefined, done: true };
      }
      const current = a;
      [a, b] = [b, a + b];
      count += 1;
      return { value: current, done: false };
    }
  };
}

The returned object closes over the a, b, and count variables, so each call to next() picks up exactly where the last one left off. Call it in a loop to drain the whole sequence:

javascriptjavascript
const fib = createFibonacciIterator(7);
 
let result = fib.next();
while (!result.done) {
  console.log(result.value);
  result = fib.next();
}
// 0, 1, 1, 2, 3, 5, 8

The iterator encapsulates three pieces of state and exposes only the next method. The consumer does not know or care how the values are computed. This is the core value of the iterator pattern: the consumer and the data source are completely decoupled.

Making a Custom Iterable

To make your own data structure work with for...of, implement a Symbol.iterator method:

javascriptjavascript
const playlist = {
  songs: ["Track A", "Track B", "Track C"],
 
  [Symbol.iterator]() {
    let index = 0;
    const songs = this.songs;
 
    return {
      next() {
        if (index >= songs.length) {
          return { value: undefined, done: true };
        }
        return { value: songs[index++], done: false };
      }
    };
  }
};

The [Symbol.iterator] method builds and returns a brand new iterator object every time it runs, with its own private index variable. That is what lets the playlist be iterated more than once:

javascriptjavascript
for (const song of playlist) {
  console.log(`Now playing: ${song}`);
}
// Now playing: Track A
// Now playing: Track B
// Now playing: Track C
 
console.log([...playlist]); // ["Track A", "Track B", "Track C"]

Symbol.iterator must return a fresh iterator every time it is called. This is why for...of works: it calls the method, gets a new iterator, and walks through it. If it returned the same iterator every time, you could only iterate once.

Iterators That Return Themselves

An iterator can also be iterable by having its Symbol.iterator method return itself:

javascriptjavascript
const rangeIterator = {
  from: 1,
  to: 3,
  current: 1,
 
  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },
 
  next() {
    if (this.current > this.to) {
      return { value: undefined, done: true };
    }
    return { value: this.current++, done: false };
  }
};

Because [Symbol.iterator] returns this instead of a new object, rangeIterator can be called directly as an iterator or handed to a for...of loop:

javascriptjavascript
// Works as an iterator
console.log(rangeIterator.next()); // { value: 1, done: false }
 
// Also works with for...of (resets current to from first)
for (const n of rangeIterator) {
  console.log(n);
}
// 1, 2, 3

When Symbol.iterator returns this, the object acts as both the iterable and the iterator. This is convenient but means the iterator is stateful and mutable. Generators, covered in /javascript/javascript-generators-deep-dive-full-guide, return iterators that follow this pattern by default.

The Optional Return Method

Iterators can optionally define a return() method. JavaScript calls it when iteration stops early, whether by a break statement, a return statement, or an error:

javascriptjavascript
const closableIterator = {
  values: [1, 2, 3, 4, 5],
  index: 0,
 
  [Symbol.iterator]() {
    return this;
  },
 
  next() {
    if (this.index >= this.values.length) {
      return { value: undefined, done: true };
    }
    return { value: this.values[this.index++], done: false };
  },
 
  return() {
    console.log("Cleaning up: iteration stopped early");
    this.index = this.values.length; // Mark as done
    return { value: undefined, done: true };
  }
};

Breaking out of the loop early below still triggers the return method, giving the iterator a chance to clean up before it is abandoned:

javascriptjavascript
for (const value of closableIterator) {
  console.log(value);
  if (value === 2) break;
}
// 1
// 2
// Cleaning up: iteration stopped early

Use the return method to release resources like file handles, network connections, or DOM event listeners when iteration is abandoned before completion.

Iterators vs Other Patterns

PatternUse when
IteratorYou need to produce a sequence of values on demand, especially for custom data structures
GeneratorYou want to write iterator logic without manually tracking state, using yield
ArrayThe data already exists and fits in memory, and you need random access
CallbackYou need to pass each value to a function, not pull values one at a time

Iterators are a pull-based model: the consumer requests the next value. Callbacks are push-based: the producer sends values to a function.

Generators are a simpler syntax for writing iterators. For the higher-level iterable protocol that ties all of this together, see /javascript/javascript-iterable-protocol-complete-guide.

Rune AI

Rune AI

Key Insights

  • An iterator is any object with a next() method that returns { value, done } objects.
  • The iterator protocol defines how values are produced one at a time from any source.
  • Built-in iterables (arrays, strings, Maps, Sets) return iterators from their Symbol.iterator method.
  • for...of, spread, and Array.from all consume iterators under the hood.
  • Custom iterators let you define iteration logic for your own data structures.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between an iterator and an iterable?

An iterable is an object that has a Symbol.iterator method. Calling that method returns an iterator. An iterator is the object with a next() method that produces values. Arrays are iterables. The object returned by arr[Symbol.iterator]() is an iterator. You can consume an iterable many times (each time you get a fresh iterator), but an iterator is usually consumed once.

Why build a custom iterator instead of using an array?

When your data is computed on the fly or is too large to store in memory. An iterator over a database cursor produces rows one at a time without loading the entire table. An iterator over an infinite sequence computes values on demand. Arrays cannot do either.

Conclusion

Iterators are the mechanism behind every loop, spread, and destructuring operation in JavaScript. They are the 'how' of iteration: a simple protocol where an object produces values one at a time through a next() method. Understanding iterators demystifies for...of, spread, and Array.from, and gives you the tools to make any data structure work with JavaScript's built-in iteration syntax.