Iterators & Iterables
Every time you write for…of , spread an array with [...items] , or destructure values, a hidden protocol is doing the work. That protocol is the iteration protocol , built on Symbol.iterator .
Learn Iterators & Iterables in our free JavaScript course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a quick…
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Understanding it turns "magic" syntax into something you can read, debug, and even build yourself for your own data structures.
📼 Real-World Analogy: Think of a museum audio guide tour:
JavaScript splits "being loopable" into two cooperating pieces:
That is the entire contract. Once an object honours it, for…of , spread ... , destructuring, Array.from , and Promise.all all "just work" on it.
A for…of loop is not magic — it is a small, predictable recipe. When you write for (const x of thing) , the engine:
Here is the same loop written both ways so you can see the equivalence:
This is exactly why for (const ch of "héllo") works but for (const x of {' '}) throws "is not iterable".
Languages like Python have a built-in range() . JavaScript doesn't, so let's build one. We want for (const n of range(1, 5)) to print 1 through 5 without ever creating an array in memory — values are produced one at a time, on demand.
Notice we never built an array of a million numbers — range(1, 1000000) would use almost no memory because each value appears only when next() asks for it. That laziness is the real power of iterators.
Once your object is iterable, a whole family of syntax accepts it:
These lines build and drain an iterator, but they are scrambled. Put them in the correct order so the program logs each color and finally done .
Why: you must create the data (D) before asking it for an iterator (B). You need a first next() result (C) before the while can test done (E). Inside the loop you print the current value (F) and only then advance (A); forgetting line A is the classic infinite-loop bug because result.done would never become true .
a false — the first next() returns value "a" with done: false ; the second returns "b" and its done is still false because there is one more step (the empty final call) before the string is exhausted.
It throws TypeError: object is not iterable . Plain objects have no Symbol.iterator . Use Object.values(obj) or Object.entries(obj) to loop instead.
[ 1, 2, 3 ] — a Set is iterable and only stores unique values, so spreading it produces a deduplicated array. This is the idiomatic "remove duplicates" trick.
Up next: Generators — the cleanest way to create iterators without all the boilerplate! ⚙️
Practice quiz
What makes an object an iterable?
- It has a next() method
- It has a method keyed by Symbol.iterator
- It is an array
- It has a length property
Answer: It has a method keyed by Symbol.iterator. An iterable has a Symbol.iterator method that returns an iterator.
What must an iterator's next() method return each call?
- Just the value
- An object with value and done properties
- A Promise
- true or false
Answer: An object with value and done properties. next() returns { value, done }; done stays false until iteration finishes.
Why does for...of throw on a plain object like { a: 1 }?
- Objects are too large
- Plain objects have no Symbol.iterator
- for...of only works on numbers
- You must use let, not const
Answer: Plain objects have no Symbol.iterator. Plain objects do not implement Symbol.iterator, so they are not iterable. Use Object.entries/values.
What does each step of iterating a Map yield?
- Just the keys
- Just the values
Maps are iterable and yield a [key, value] pair on each step.
What is logged? console.log([...new Set([1, 1, 2, 3, 3])]);
A Set is iterable and stores only unique values, so spreading it deduplicates the array.
What is the first thing a for...of loop does internally?
for...of first calls Symbol.iterator to obtain an iterator, then repeatedly calls next().
A custom range() iterable that yields values on demand mainly avoids what?
- Type errors
- Building a large array in memory
- Using Symbol.iterator
- Calling next()
Answer: Building a large array in memory. Lazy iterators produce each value only when asked, so range(1, 1000000) uses almost no memory.
Which of these is NOT iterable by default?
- Array
- String
- A plain object
- Set
Answer: A plain object. Arrays, strings, Maps, and Sets are iterable; a plain {} object is not.
After an iterator's done becomes true, what happens if you keep calling next()?
- It restarts from the beginning
- It stays finished — you must get a fresh iterator to start over
- It throws an error
- It returns the last value again
Answer: It stays finished — you must get a fresh iterator to start over. A drained iterator is finished; call Symbol.iterator again for a new iterator to iterate again.
Which syntax does NOT consume an iterable?
Spread, Array.from, and destructuring all consume iterables; .length is just a property, not iteration.