Skip to main content

Iterator

An iterator is an object that returns the elements of some sequence one by one on every call to its next() method. Formally: an iterator is an object implementing a next() method that returns an object of the shape { value: any, done: boolean }.

Theory

TL;DR

  • An iterator is an object with a next() method that returns { value, done }.
  • When done: true the iteration is over, and value is usually undefined at that point.
  • An iterable is an object that has a [Symbol.iterator]() method returning an iterator.
  • for...of, spread ..., Array.from() and destructuring all work through exactly this protocol.
  • Built-in iterables: Array, String, Map, Set, arguments, NodeList, typed arrays.
  • A function* generator creates an iterator automatically, with no manual return { value, done }.

Quick example

javascript
function createIterator(arr) { let i = 0; return { next() { if (i < arr.length) { return { value: arr[i++], done: false }; } else { return { value: undefined, done: true }; } } }; } const iterator = createIterator(['a', 'b', 'c']); 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 }

When done: true, the iteration ends.

What an iterator does inside for...of

When you write:

javascript
for (const item of ['a', 'b', 'c']) { console.log(item); }

JavaScript does roughly the following under the hood:

javascript
const iterable = ['a', 'b', 'c']; const iterator = iterable[Symbol.iterator](); // creates an iterator while (true) { const { value, done } = iterator.next(); if (done) break; console.log(value); }

In other words, the loop itself knows nothing about arrays: it only knows the next() protocol.

Iterables and Symbol.iterator

An object is iterable if it has the method:

javascript
obj[Symbol.iterator]()

which returns an iterator. Symbol.iterator is a special symbol that defines the "iterability" of an object, and JavaScript calls that method automatically whenever an object has to be walked.

Examples of built-in iterable structures:

  • Array
  • String
  • Map
  • Set
  • arguments
  • NodeList (in the browser)
  • TypedArray

Your own iterable is written like this:

javascript
const range = { from: 1, to: 4, [Symbol.iterator]() { let current = this.from; let last = this.to; return { next() { if (current <= last) { return { value: current++, done: false }; } else { return { done: true }; } } }; } }; for (const num of range) { console.log(num); // 1, 2, 3, 4 }

When JavaScript meets for...of, it calls range[Symbol.iterator]() and uses the iterator it gets back.

A string is iterable too, and it is walked by code points rather than by bytes:

javascript
const str = 'Hi'; const iterator = str[Symbol.iterator](); console.log(iterator.next()); // { value: 'H', done: false } console.log(iterator.next()); // { value: 'i', done: false } console.log(iterator.next()); // { value: undefined, done: true }

Iterators with spread, destructuring and Array.from

All of these constructs use an iterator internally:

javascript
const set = new Set([1, 2, 3]); console.log([...set]); // [1, 2, 3] console.log(Array.from(set)); // [1, 2, 3] const [a, b] = set; console.log(a, b); // 1 2

That is exactly why spread works with Set and Map even though they are not arrays: having [Symbol.iterator]() is enough.

Generators are automatic iterators

A generator (function*) is a function that creates an iterator by itself.

javascript
function* numbers() { yield 1; yield 2; yield 3; } const iterator = numbers(); console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: 3, done: false } console.log(iterator.next()); // { value: undefined, done: true }

And also:

javascript
for (const n of numbers()) { console.log(n); // 1, 2, 3 }

Generators are convenient because you do not have to write return { value, done } by hand: the engine builds those objects from each yield for you.

Iterability of modern structures:

StructureIterable?Returns
Arrayyesvalues
Stringyescharacters
Mapyes[key, value] pairs
Setyesvalues
Objectnonothing, but you can add [Symbol.iterator]() by hand
argumentsyesthe argument values

A short summary:

TermDescription
IteratorAn object with a next() method returning { value, done }
IterableAn object that has a [Symbol.iterator]() method
Symbol.iteratorThe symbol pointing at the method that creates an iterator
Used infor...of, spread, Array.from, destructuring
Generator (function*)A simplified way to create an iterator

Common mistakes

  • Confusing an iterator with an iterable. An iterator has next(), an iterable has [Symbol.iterator](). An array is an iterable, while arr[Symbol.iterator]() is already an iterator.
  • Expecting for...of to work on a plain object. {} has no [Symbol.iterator](), so you get a TypeError. For objects you use Object.keys(), Object.values() or Object.entries().
  • Walking the same iterator twice. It is single-use: after { done: true } it is exhausted forever, and a second for...of yields nothing.
  • Forgetting to return { value, done } as an object. If next() returns a bare value, the engine throws a TypeError.
  • Mixing up for...of and for...in. for...in walks keys, inherited ones included, and has nothing to do with the iteration protocol.
  • Writing an unbounded iterable with no limit. A generator with while (true) inside a for...of with no break and no take will freeze the tab.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.