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: truethe iteration is over, andvalueis usuallyundefinedat 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 manualreturn { value, done }.
Quick example
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:
for (const item of ['a', 'b', 'c']) {
console.log(item);
}JavaScript does roughly the following under the hood:
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:
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:
ArrayStringMapSetargumentsNodeList(in the browser)TypedArray
Your own iterable is written like this:
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:
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:
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 2That 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.
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:
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:
| Structure | Iterable? | Returns |
|---|---|---|
Array | yes | values |
String | yes | characters |
Map | yes | [key, value] pairs |
Set | yes | values |
Object | no | nothing, but you can add [Symbol.iterator]() by hand |
arguments | yes | the argument values |
A short summary:
| Term | Description |
|---|---|
| Iterator | An object with a next() method returning { value, done } |
| Iterable | An object that has a [Symbol.iterator]() method |
Symbol.iterator | The symbol pointing at the method that creates an iterator |
| Used in | for...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, whilearr[Symbol.iterator]()is already an iterator. - Expecting
for...ofto work on a plain object.{}has no[Symbol.iterator](), so you get aTypeError. For objects you useObject.keys(),Object.values()orObject.entries(). - Walking the same iterator twice. It is single-use: after
{ done: true }it is exhausted forever, and a secondfor...ofyields nothing. - Forgetting to return
{ value, done }as an object. Ifnext()returns a bare value, the engine throws aTypeError. - Mixing up
for...ofandfor...in.for...inwalks 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 afor...ofwith nobreakand notakewill freeze the tab.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.