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.