Iterables and the Iterator Protocol
In short: for...of, the spread operator ([...x]), and destructuring
all work through one shared mechanism: an iterable is any value with a
[Symbol.iterator] method, which returns an iterator — an object with
a next() method. Calling next() returns { value, done }: the current
item, and whether there are more to come. for...of calls next()
repeatedly for you, stopping the instant done is true. Arrays,
strings, Maps and Sets all come with this built in; a plain object
does not, unless you add it yourself.
![A conveyor with two items and a next() lever: it.next() pulls one item and moves the belt forward, returning { value: "Blue Hour", done: false }; once the belt is empty, next() gives { value: undefined, done: true } and a "done" light — for...of and [...x] call next() for you, stopping the moment done is true](/api/assets/javascript/assets/c080a725286ec6c1.png)
Pull items from an array's iterator by hand
Every array already has a [Symbol.iterator] method; calling it directly
shows exactly what for...of does under the hood:
The first two calls return { value: "Blue Hour", done: false } and
{ value: "Red Dawn", done: false }. The third, with nothing left,
returns { value: undefined, done: true } — this is the exact signal
for...of watches for to know when to stop.
Make a plain object iterable
A plain object has no built-in [Symbol.iterator] — add one, returning an
object with its own next(), and for...of and the spread operator both
start working on it:
for...of calls playlist[Symbol.iterator]() once to get the iterator,
then next() repeatedly, printing each song and stopping cleanly once
done is true. [...playlist] uses that exact same protocol to build a
real array, ["Blue Hour", "Red Dawn", "Green Tide"].
The trap: assuming every object works in for...of
Without a [Symbol.iterator] method, for...of doesn't loop zero times
or skip silently — it throws immediately, before the loop body ever runs:
This throws TypeError: plain is not iterable. Plain objects are
deliberately not iterable by default — for their key/value pairs, use
for...in, or Object.entries(plain) (which returns a real, iterable
array) inside a for...of.
Quick practice
What does arr[Symbol.iterator]().next() return for a non-empty array's first call?
What does an iterator's next() return once there's nothing left to give?
What method must an object have to be usable in for...of?
What happens when you use for...of on a plain object with no [Symbol.iterator]?
What does [...playlist] do, given playlist has a working [Symbol.iterator]?
🎯 Your turn
A plain object const playlist = { songs: ["a", "b"] }; throws
TypeError: playlist is not iterable when used in
for (const s of playlist) {}. Explain exactly why, and what you would
add to playlist to fix it, in terms of Symbol.iterator, next(),
value and done.
Write your explanation and submit it — there's no code to run here, just your reasoning.
