JavaScript Interview Prep
ES6+ Features

Iterators and Iterables

The Protocol Behind for...of

LinkedIn Hook

You write for (const x of myObj) { ... } and JavaScript throws TypeError: myObj is not iterable.

Why? Because plain objects don't implement the iterator protocol. Arrays, strings, Sets, Maps, NodeLists, and arguments all 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 Range class 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


Iterators and Iterables thumbnail


What You'll Learn

  • The two protocols: iterable (has [Symbol.iterator]()) and iterator (has next() -> { value, done })
  • How to make a custom class work with for...of, spread, destructuring, and Array.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]

Iterators and Iterables visual 1


Common Mistakes

  • Expecting plain objects to work with for...of — they don't. Only iterables do. If you need key/value iteration, call Object.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 second for...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 a next() 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 a next() method that returns { value, done }. Once implemented, the object works with for...of, spread, destructuring, and Array.from.

Q: What consumes iterables in JavaScript?

for...of loops, spread operator (...), destructuring assignment, Array.from(), Promise.all(), Map and Set constructors, yield* in generators, and any API that accepts an iterable.

Q: What is Symbol.iterator?

Symbol.iterator is 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.

On this page