cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

The iteration protocols

Learn the Symbol.iterator and next() protocol behind for...of, spread, Array.from, destructuring, Map, Set, strings, and your own custom iterable objects.

By the end, you can
  • 01
    Explain the protocolDescribe how [Symbol.iterator]() returns an iterator with next() results.
  • 02
    Predict consumersTrace how for...of, spread, destructuring, and Map consume iterables.
  • 03
    Build and debug iterablesCreate a custom range and fix TypeError: object is not iterable.

The hidden agreement behind loops

In the for…of & for…in lesson, you used for...of to ask arrays and strings for their values. This lesson opens the hood. An iterable is any value that can produce an iterator. An iterator is the object that hands values out one at a time.

That small agreement is why one set of tools works with many kinds of data: for...of, spread, Array.from, destructuring, Promise.all, and new Map all know how to consume iterable values.

Real-life analogyIterable as a vending machine

Ask the machine for a dispenser, then press the dispenser for one item at a time. The machine can hand another customer a fresh dispenser. One dispenser remembers where it is.

In real life: A vending machine
In JavaScript: An iterable object
In real life: The button that hands you a dispenser
In JavaScript: The [Symbol.iterator]() method
In real life: The dispenser
In JavaScript: The iterator object
In real life: Pressing once for one snack
In JavaScript: Calling next() for one { value, done } result

Where the analogy stops: A real vending machine stores a fixed shelf of snacks. A JavaScript iterable may calculate values lazily, read from another data source, or even represent a sequence that is too large to store.

The two protocols
TermRequired methodWhat it returnsTypical example
Iterable[Symbol.iterator]()An iterator objectArray, string, Map, Set, custom range
Iteratornext(){ value, done } result objectsThe object returned by an array iterator
Iterable iteratorBoth methodsItself from [Symbol.iterator]()Built-in array, string, Map, and Set iterators

The precision matters because the words sound almost the same. An array is iterable, but it is not itself an iterator. The object returned by array[Symbol.iterator]() is the iterator.

Symbol.iterator: the method consumers look for

FOUNDATION

The iterable protocol is a method keyed by the built-in symbol Symbol.iterator. Symbols, from the Symbols lesson, make excellent protocol keys because they do not collide with ordinary property names like iterator.

The smallest useful shapePop out in the code editor (opens in a new tab)JavaScript
const iterable = {  [Symbol.iterator]() {    return iterator;  },}; const iterator = {  next() {    return { done: true };  },};

Real iterables usually create the iterator inside the method, so each call starts fresh. Below, range(1, 5, step) creates a custom iterable. The iterable is reusable; the iterator it creates is one trip through the numbers.

Protocol, not inheritance

JavaScript does not require a special class. If an object has a callable [Symbol.iterator] method returning an object with next(), consumers can use it.

next() and done: the result objects

STEP THROUGH

An iterator’s job is tiny and strict: every next() call returns an object. When there is a value, the result is { value, done: false }. When the sequence is over, the result has done: true. A consumer stops there.

Step through a custom range iterable
Step 0 of 10Ready
Your turn: follow the blue line

Predict each result object before stepping. Change the step to make the same iterable count differently.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const iterator = numbers[Symbol.iterator](); console.log(iterator.next());console.log(iterator.next());console.log(iterator.next());console.log(iterator.next()); function range(start, end, step = 1) {  return {    [Symbol.iterator]() {      let current = start;      return {        next() {          if (current > end) return { done: true };          const value = current;          current = current + step;          return { value, done: false };        },      };    },  };}
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change line 1: how far should each next() advance?

Changing the step starts a fresh run. Predict the next result object before stepping.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Notice the final result. for...of looks at done; if it is true, the loop body does not run. Even if a custom iterator includes a value on that final result, a for...of loop ignores it.

Press next() yourself
Manual next() sourcePop out in the code editor (opens in a new tab)JavaScript
const source = "A😀B";const iterator = source[Symbol.iterator](); iterator.next(); // { value: "A", done: false }iterator.next(); // { value: "😀", done: false }iterator.next(); // { value: "B", done: false }iterator.next(); // { value: undefined, done: true }
Results0 calls

No calls yet.

Step 0 of 4string

Choose an iterable, then press next() to ask for one result object at a time.

A manual consumer for arrays, strings, Map, Set, and the lesson's custom range.
String iteration is by code point

The Unicode & string internals lesson explains why "😀".length can be 2. Iteration is friendlier: the string iterator yields that emoji as one value.

Consumers: who keeps pressing next()?

INTERACTIVE

A consumer is anything that accepts an iterable and pulls values from it. The syntax may look different, but under the hood each consumer asks for an iterator and keeps calling next() until it has enough or sees done: true.

Real-life analogyCustomers pressing the dispenser

Some customers empty the dispenser. Some leave early. When they leave early, JavaScript politely gives the iterator a chance to clean up by calling return().

In real life: A customer keeps pressing until the dispenser says empty
In JavaScript: for...of, spread, and Array.from consume until done: true
In real life: A customer only needs two tickets and leaves
In JavaScript: Destructuring [a, b] = iterable stops early
In real life: A polite customer returns the dispenser
In JavaScript: Early exit calls return() if the iterator provides it

Where the analogy stops: The customer does not choose the next snack. The iterator controls order and values; the consumer only requests the next result.

Watch consumers press next()
The counting iterablePop out in the code editor (opens in a new tab)JavaScript
function counting(limit) {  let nextCalls = 0;  let returnCalls = 0;  return {    stats: () => ({ nextCalls, returnCalls }),    [Symbol.iterator]() {      let value = 1;      return {        next() {          nextCalls = nextCalls + 1;          return value <= limit            ? { value: value++, done: false }            : { done: true };        },        return() {          returnCalls = returnCalls + 1;          return { done: true };        },      };    },  };}
Consumer resultdestructuring
Result1,2
next() calls2
return() calls1
Try it yourself

destructuring produced 1,2; it called next() 2 time(s) and return() 1 time(s).

Every count comes from a real iterable object with instrumented next() and return() methods.

The most surprising row is destructuring. [a, b] = iterable only needs two values. If the iterator has more to give and defines return(), JavaScript closes it. A break or a thrown error inside for...of does the same kind of early close.

Iterables vs iterators: reusable machine, one-shot dispenser

INTERACTIVE
Real-life analogyA one-shot iterator is a roll of tickets

Once you tear a ticket off a roll, it is gone from that roll. To start over, you do not tape tickets back on; you ask the booth for a fresh roll.

In real life: A roll of tickets
In JavaScript: One iterator object
In real life: Tearing off ticket 1, then ticket 2
In JavaScript: Calling next() advances the saved position
In real life: An empty roll stays empty
In JavaScript: A done iterator does not rewind
In real life: Ask the booth for a new roll
In JavaScript: Call the iterable’s [Symbol.iterator]() again

Where the analogy stops: Some custom iterators can choose unusual behavior, but built-in iterators are one-way. Treat iterators as consumable.

Reuse the iterator or ask for a fresh one?
Iterator exhaustionPop out in the code editor (opens in a new tab)JavaScript
const iterable = ["tea", "cake"];const iterator = iterable[Symbol.iterator](); console.log(iterator.next());console.log(iterator.next());console.log(iterator.next());console.log(iterator.next()); const fresh = iterable[Symbol.iterator]();console.log(fresh.next());console.log(iterator[Symbol.iterator]() === iterator);
Outputwaiting

Not run yet.

Try it yourself

Run it, then compare the original iterator with the fresh one.

This lab separates the reusable iterable from the one-shot iterator it creates.

Built-in iterator objects are a useful special case: they are iterable themselves. That means iterator[Symbol.iterator]() === iterator. A consumer can accept an iterator directly, but it will consume the iterator from its current position, not from the beginning.

Built-in iterables you already use

JavaScript ships many values that already speak the protocol. Arrays, strings, Maps, Sets, typed arrays, and arguments are iterable. In browsers, DOM collections such as NodeList are iterable in modern engines too.

Common built-in iterables
ValueDefault valuesExample use
ArrayEach array itemfor (const item of items)
StringUnicode code points[..."A😀B"]
Map[key, value] pairs in insertion orderfor (const [key, value] of map)
SetEach unique value in insertion ordernew Set(array) and for...of
TypedArrayNumbers from binary storageUint8Array values
Browser NodeListNodes in document orderdocument.querySelectorAll(...) in most modern browsers

Map’s default is especially practical. A Map yields pairs, so destructuring fits naturally: for (const [key, value] of map). You met Map and Set in the Map & Set lesson; this is the protocol that lets them plug into the rest of the language.

Plain objects are not iterable by default

INTERACTIVE

Plain objects are wonderful key/value bags, but they do not have a default order that for...of should treat as “the values.” That is why this code throws a real TypeError. Convert the object to an iterable list, or add your own iterator when the object has a meaningful sequence.

Fix TypeError: object is not iterable
The failing loopPop out in the code editor (opens in a new tab)JavaScript
const plain = { a: 1, b: 2 }; for (const item of plain) {  console.log(item);} for (const entry of Object.entries(plain)) {  console.log(entry);}
Resulterror
Press Try plain object to run the failing loop in your browser.
Try it yourself

The real runtime error appears because the plain object has no [Symbol.iterator]() method.

Plain object properties are not automatically a sequence. Convert them or define a sequence explicitly.

Most of the time, Object.entries(object) is the clean fix. It gives an array of pairs, and arrays are iterable. You saw this pattern in Object.keys, values & entries and Destructuring.

Where you’ll use this

Protocol thinking helps you write flexible code. Instead of accepting only arrays, a helper can accept any iterable: arrays from one caller, Sets from another, a Map’s keys, or a custom range. It also helps you debug errors from spread, destructuring, Promise.all, and new Map because you know what method they are searching for.

A helper that accepts any iterablePop out in the code editor (opens in a new tab)JavaScript
function firstTwoLabels(items) {  const iterator = items[Symbol.iterator]();  const first = iterator.next();  const second = iterator.next();  return [first.value, second.value];} console.log(firstTwoLabels(new Set(["draft", "review", "done"])));
Iterable, iterator, both, or neither?
  • ["red", "green"]
  • ["red"][Symbol.iterator]()
  • { name: "Ada" }
  • { next() { return { done: true }; } }
  • "A😀B"
  • new Map([["a", 1]])
  • range(1, 3)
  • new Set([1, 2]).values()
Try it yourself
0 of 8 correct

Sort each value by the protocol it speaks. Ask: does it have [Symbol.iterator]()? Does it have next()?

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Iterator helpers are next-but-one

The Iterator helpers lesson comes after Generators. Helpers add methods such as mapping and filtering to iterators, but they rely on the protocol you just learned. For now, text only: remember the foundation before the convenience methods.

Common misconceptions

“Anything with values is iterable.”

No. for...of looks for [Symbol.iterator](). A plain object has values but no default iterable protocol.

“An iterable and an iterator are the same thing.”

An iterable can make an iterator. An iterator has next() and remembers progress. Some objects, especially built-in iterators, are both.

“The final value always matters.”

Once done is true, for...of stops and ignores the value on that result.

“Iterators rewind after they finish.”

Finished iterators stay finished. Ask the iterable for a new iterator to start again.

“Early exit just abandons the iterator.”

If the iterator has return(), JavaScript calls it when a consumer leaves early. That gives custom iterators a cleanup hook.

Practice: trace and build iterables

5 EXERCISES
Exercise 1 · Warm-upPredict manual next calls

Trace the iterator result objects. What three things print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const letters = ["a", "b"];
const iterator = letters[Symbol.iterator]();
console.log(iterator.next().value);
console.log(iterator.next().done);
console.log(iterator.next().done);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite a small range iterable

    Run or read the program. What does spreading the custom range print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function range(start, end) {
      return {
        [Symbol.iterator]() {
          let current = start;
          return {
            next() {
              if (current > end) return { done: true };
              return { value: current++, done: false };
            },
          };
        },
      };
    }
    console.log([...range(2, 4)].join(","));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFeed entries to a Map

      What value prints after new Map consumes the pair iterable?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const entries = [["theme", "dark"], ["font", "mono"]];
      const settings = new Map(entries);
      console.log(settings.get("font"));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeFix a plain object loop

        Change the loop so it prints name=Ada and role=admin.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const user = { name: "Ada", role: "admin" };
        for (const item of user) {
          console.log(item); // TypeError: user is not iterable
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeNotice early closing

          Predict the two lines printed by a for...of loop that breaks after the first value.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const iterable = {
            [Symbol.iterator]() {
              let value = 1;
              return {
                next() {
                  return value <= 3 ? { value: value++, done: false } : { done: true };
                },
                return() {
                  console.log("closed");
                  return { done: true };
                },
              };
            },
          };
          for (const value of iterable) {
            console.log(value);
            break;
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            8 QUESTIONS

            Predict first. Every answer explains the protocol step that makes it right or wrong.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Which method makes an object iterable?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this manual iterator code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const iterator = ["x"][Symbol.iterator]();
              console.log(iterator.next().value);
              console.log(iterator.next().done);

              Choose an answer to see the explanation.

            3. Question 3 of 8What does for...of do with the value on a { done: true } result?

              Choose an answer to see the explanation.

            4. Question 4 of 8What does string iteration print here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              for (const character of "A😀B") {
                console.log(character);
              }

              Choose an answer to see the explanation.

            5. Question 5 of 8Which consumer calls return() when it stops early?

              Choose an answer to see the explanation.

            6. Question 6 of 8What does Map iteration yield by default?

              Choose an answer to see the explanation.

            7. Question 7 of 8What happens with this plain object loop?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const plain = { a: 1 };
              for (const item of plain) {
                console.log(item);
              }

              Choose an answer to see the explanation.

            8. Question 8 of 8Which statement about built-in iterators is true?

              Choose an answer to see the explanation.

            Key takeaways

            • An iterable has a [Symbol.iterator]() method that returns an iterator.
            • An iterator has next(), which returns { value, done } result objects.
            • for...of, spread, Array.from, destructuring, Promise.all, and new Map are iterable consumers.
            • Early exit calls an iterator’s return() method if it exists.
            • Plain objects are not iterable by default; use Object.entries or define a custom iterator.

            Remember the one-liner.
            An iterable makes an iterator; an iterator’s next() returns { value, done }.

            Up next: Generators, functions that produce iterator values with yield.

            CompleteFrontend Clear concepts. Working examples.