Iterators and Iterables
The Protocol Behind for...of
LinkedIn Hook
You write
for (const x of myObj) { ... }and JavaScript throwsTypeError: myObj is not iterable.Why? Because plain objects don't implement the iterator protocol. Arrays, strings, Sets, Maps, NodeLists, and
argumentsall do — and they all share the same contract: an object that produces an iterator when you call[Symbol.iterator]().The iterator protocol is the quiet standard behind
for...of, spread (...), destructuring,Array.from,Promise.all, and every generator in existence. Implement it once on a custom class and every consumer "just works."In this lesson I unpack the two protocols (iterable + iterator), build a
Rangeclass from scratch, and walk through everything that consumes iterables under the hood.If you've ever copy-pasted
Array.from(...)without knowing why it works — this lesson is the answer.Read the full lesson -> [link]
#JavaScript #InterviewPrep #ES6 #Iterators #ForOf #Symbol #CodingInterview
What You'll Learn
- The two protocols: iterable (has
[Symbol.iterator]()) and iterator (hasnext()->{ value, done }) - How to make a custom class work with
for...of, spread, destructuring, andArray.from - Every language construct that silently calls
[Symbol.iterator]()on your behalf
The Bookmark Metaphor
An iterator is like a bookmark in a book. Each time you say "next page," it gives you the current page and moves forward. When you reach the end, it says "done." An iterable is the book itself — anything that knows how to produce a bookmark.
The Iterator Protocol
An object is an iterator if it has a next() method that returns { value, done }:
// Manual iterator
function createCountdown(start) {
let current = start;
return {
next() {
if (current > 0) {
return { value: current--, done: false };
}
return { value: undefined, done: true };
}
};
}
const countdown = createCountdown(3);
countdown.next(); // { value: 3, done: false }
countdown.next(); // { value: 2, done: false }
countdown.next(); // { value: 1, done: false }
countdown.next(); // { value: undefined, done: true }
The Iterable Protocol — Symbol.iterator
An object is iterable if it has a [Symbol.iterator]() method that returns an iterator:
// Built-in iterables
const arr = [10, 20, 30];
const iter = arr[Symbol.iterator]();
iter.next(); // { value: 10, done: false }
iter.next(); // { value: 20, done: false }
iter.next(); // { value: 30, done: false }
iter.next(); // { value: undefined, done: true }
// Strings are iterable
const strIter = "Hi"[Symbol.iterator]();
strIter.next(); // { value: "H", done: false }
strIter.next(); // { value: "i", done: false }
Custom Iterable — Range Class
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const { end, step } = this;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
// Now Range works with for...of, spread, destructuring
const range = new Range(1, 5);
for (const n of range) {
console.log(n); // 1, 2, 3, 4, 5
}
console.log([...range]); // [1, 2, 3, 4, 5]
const [first, second] = range; // first = 1, second = 2
console.log(Array.from(range)); // [1, 2, 3, 4, 5]
// With step
const evens = new Range(0, 10, 2);
console.log([...evens]); // [0, 2, 4, 6, 8, 10]
for...of Consuming Iterators
// for...of works with any iterable
for (const char of "Hello") {
console.log(char); // H, e, l, l, o
}
for (const [key, value] of new Map([["a", 1], ["b", 2]])) {
console.log(key, value); // a 1, b 2
}
for (const item of new Set([1, 2, 3])) {
console.log(item); // 1, 2, 3
}
// Plain objects are NOT iterable by default
// for (const x of { a: 1 }) {} // TypeError: not iterable
Spread with Iterables
// Spread works with any iterable
const str = "Hello";
const chars = [...str]; // ["H", "e", "l", "l", "o"]
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1, 2, 3]
// Combine iterables
const combined = [...new Range(1, 3), ...new Range(7, 9)];
// [1, 2, 3, 7, 8, 9]
Common Mistakes
- Expecting plain objects to work with
for...of— they don't. Only iterables do. If you need key/value iteration, callObject.entries(obj)which returns an iterable array. - Returning an iterator from
[Symbol.iterator]()that doesn't itself implement[Symbol.iterator]()(and therefore can't be used with a secondfor...of). Add[Symbol.iterator]() { return this; }to the iterator object so it is also an iterable. - Mutating the underlying collection while iterating — the iterator may produce stale values, skip elements, or visit the same element twice depending on the collection type. Snapshot the data first if you plan to mutate.
Interview Questions
Q: What is the difference between an iterator and an iterable?
An iterable is an object with a
[Symbol.iterator]()method that returns an iterator. An iterator is an object with anext()method that returns{ value, done }. Arrays, strings, Maps, and Sets are built-in iterables. Plain objects are NOT iterable by default.
Q: How would you make a custom object iterable?
Implement
[Symbol.iterator]()on the object (or its class). This method must return an object with anext()method that returns{ value, done }. Once implemented, the object works withfor...of, spread, destructuring, andArray.from.
Q: What consumes iterables in JavaScript?
for...ofloops, spread operator (...), destructuring assignment,Array.from(),Promise.all(),MapandSetconstructors,yield*in generators, and any API that accepts an iterable.
Q: What is Symbol.iterator?
Symbol.iteratoris a well-known symbol whose value is the key used by the iterable protocol. Any object with a[Symbol.iterator]()method is iterable. Using a symbol (rather than a string key) prevents name collisions with regular properties.
Quick Reference — Cheat Sheet
ITERATORS & ITERABLES — QUICK MAP
Protocols:
Iterable -> obj[Symbol.iterator]() returns an Iterator
Iterator -> obj.next() returns { value, done }
Built-in iterables:
Array, String, Map, Set, NodeList, arguments,
TypedArray, generator return values.
NOT iterable by default:
Plain objects {} — use Object.entries(obj) for kv iteration.
Consumers (each calls [Symbol.iterator]() under the hood):
for...of, spread (...), destructuring, Array.from(),
Promise.all / Promise.race / Promise.any,
new Map(entries), new Set(values), yield* gen
Minimal custom iterable:
class X {
[Symbol.iterator]() {
let i = 0;
return {
next() {
return i < 3
? { value: i++, done: false }
: { value: undefined, done: true };
}
};
}
}
Previous: Modules -> import, export, and Dynamic Loading Next: Set, Map, WeakSet, WeakMap -> Purpose-Built Collections
This is Lesson 9.6 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.