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.
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:
// 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:
| Iterable | Iterator | |
|---|---|---|
| Has | A Symbol.iterator method | A next() method |
| Produces | An iterator, when its Symbol.iterator method is called | Value and done objects, one per call to next |
| Example | An array | The 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:
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:
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:
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:
| Iterable | Iterator yields |
|---|---|
| Array | Each element in order |
| String | Each Unicode code point |
| Map | key/value pairs |
| Set | Each value in insertion order |
| NodeList | Each DOM node |
| arguments | Each function argument |
| TypedArray | Each numeric element |
// 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: 82Map 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:
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:
const fib = createFibonacciIterator(7);
let result = fib.next();
while (!result.done) {
console.log(result.value);
result = fib.next();
}
// 0, 1, 1, 2, 3, 5, 8The 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:
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:
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:
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:
// 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, 3When 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:
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:
for (const value of closableIterator) {
console.log(value);
if (value === 2) break;
}
// 1
// 2
// Cleaning up: iteration stopped earlyUse 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
| Pattern | Use when |
|---|---|
| Iterator | You need to produce a sequence of values on demand, especially for custom data structures |
| Generator | You want to write iterator logic without manually tracking state, using yield |
| Array | The data already exists and fits in memory, and you need random access |
| Callback | You 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
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.
Frequently Asked Questions
What is the difference between an iterator and an iterable?
Why build a custom iterator instead of using an array?
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.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.