In short
for...of works on arrays, strings, Maps and Sets, but not on plain objects. The reason is the iterator protocol, and once you get it you can make your own things loopable.
Objects that implement the iterator protocol (`.next()` returning `{value, done}`), the underlying mechanism that makes `for...of` and spread syntax work on arrays, strings, Maps and Sets. The iterator protocol provides a common abstraction for sequential consumption, which is why many built-in collections work with `for...of`, spread, and destructuring.

Think of it like this
The clinic token machine
At a clinic, you press the button and get the next token number. At closing time it shows "no more tokens". You never see the whole list, you just keep asking "next?". An iterator is that machine: next() hands over one value until done is true.
Watch it happen
What for...of asks clinicQueue
[Symbol.iterator]()
1/7for...of first asks the object for an iterator.
The big ideas
What you need to know
Iterator: has next()
An iterator is any object with a next() method that returns { value, done }.
Iterable: has Symbol.iterator
An iterable is an object with a [Symbol.iterator]() method that returns a fresh iterator.
Built-ins are iterable
Arrays, strings, Maps, Sets and NodeLists all are. Plain objects aren't, so use Object.entries(obj).
Lots of syntax uses it
for...of, spread, array destructuring and Array.from all ask for an iterator behind the scenes.
See it in code
Walk through the code
1const clinicQueue = {2 patients: ["Asha", "Vikram", "Zoya"],3 [Symbol.iterator]() {4 let i = 0;5 return {6 next: () =>7 i < this.patients.length8 ? { value: this.patients[i++], done: false }9 : { value: undefined, done: true },10 };11 },12};13 14for (const p of clinicQueue) console.log(p); // Asha, Vikram, Zoya15console.log([...clinicQueue].length); // 31/4A plain object holding a list of patients.
next is an arrow function, so this still points to clinicQueue. Each for...of or spread calls [Symbol.iterator]() again and starts from 0.
Save yourself the pain
Common mistakes
Oops
Writing for (const x of someObject) on a plain object.
Do this instead
You'll get "someObject is not iterable". Loop over Object.entries(someObject) or Object.keys(someObject) instead.
Oops
Reusing an iterator after it's finished and expecting it to start over.
Do this instead
An iterator is used up once done is true. Get a new one from the iterable.
Your mission
Make a range
Quick check
What happens? for (const x of { a: 1 }) console.log(x);
Go deeper
- 1Manually call `.next()` on an array's iterator (`arr[Symbol.iterator]()`). Validate your understanding by applying it in a small practical example.
- 2Implement a custom iterable object using `Symbol.iterator`. Validate your understanding by applying it in a small practical example.
- 3Understand how generators are a convenient shortcut for writing iterators.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.