You will be able to explain and use JavaScript iterators to step through values one at a time, including how for...of and spread rely on the iterator protocol.
What it is
An iterator is an object that knows how to produce the next value in a sequence. JavaScript uses a small protocol: an object is iterable if it has a method at Symbol.iterator that returns an iterator. That iterator must have a next() method that returns an object with value and done.
Think of it as a manual stepping mechanism: instead of asking for all items at once, you ask for the next item until the sequence says it is finished. Related terms: iterable, iterator protocol, generator, for...of, spread syntax.
Why it matters
for...ofloops work with arrays, strings, Maps, Sets, and custom iterables.- Spread syntax and
Array.from()consume iterables. - It lets you process large or lazy sequences without building an intermediate array.
- It provides a common way to expose data from custom objects.
Syntax or steps
The smallest useful pattern is to get an iterator from an iterable and call next():
const it = arr[Symbol.iterator]();
it.next(); // { value: first, done: false }
it.next(); // { value: second, done: false }
it.next(); // { value: undefined, done: true }
To make an object iterable, define [Symbol.iterator]() and return an object with next().
Example
const numbers = [10, 20, 30];
// Manual iterator
const it = numbers[Symbol.iterator]();
console.log(it.next()); // { value: 10, done: false }
console.log(it.next()); // { value: 20, done: false }
console.log(it.next()); // { value: 30, done: false }
console.log(it.next()); // { value: undefined, done: true }
// Custom iterable: counts down from n to 1
const countdown = {
from: 3,
[Symbol.iterator]() {
let current = this.from;
return {
next() {
if (current > 0) {
return { value: current--, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const n of countdown) {
console.log(n); // 3, 2, 1
}
Part by part: numbers[Symbol.iterator]() creates an iterator for the array. Each next() call returns the next value and whether the sequence is finished. In countdown, [Symbol.iterator]() returns a fresh iterator each time, so the loop can step through 3, 2, 1 without storing them in an array.
Common mistakes
- Confusing an iterable with an iterator. An array is iterable; calling
array[Symbol.iterator]()gives an iterator. - Returning
done: truewith a meaningfulvalueand expectingfor...ofto use it. The finaldone: trueresult is usually ignored byfor...of. - Reusing one iterator after it is exhausted. Create a new iterator when you need to start over.
- Forgetting to return an object from
next(). It must return{ value, done }, not just a value.
When to use it
Use iterators when you need step-by-step access or a custom sequence. Use array methods when you already have an array and want a concise transformation.
| Need | Use | Why |
|---|---|---|
| Loop over values | for...of | Works with any iterable and gives one value at a time. |
| Transform an array | map(), filter() | Shorter and clearer when the whole collection is available. |
| Lazy or custom sequence | Iterator or generator | Produces values on demand without storing all of them. |
Practice
Guided exercise: Create an iterable called evens that yields 2, 4, 6 when used in a for...of loop.
Challenge: Modify it so each call to [Symbol.iterator]() restarts from 2.
Hint: Store the starting value inside the iterator object, not on the outer iterable, so a new iterator can begin fresh.
Quick check
Question: What does next() return when an iterator has no more values?
Answer: An object like { value: undefined, done: true }.
Summary
Iterators are JavaScript’s standard way to step through a sequence one value at a time. The key idea is the protocol: an iterable provides Symbol.iterator, and the returned iterator provides next() with value and done. This protocol powers for...of, spread, and custom lazy sequences.