Iterables

An array is the obvious thing you can loop over with for..of. But arrays are not special here. They just happen to follow a protocol, and any object that follows the same protocol becomes loopable too. That protocol is what “iterable” means.

Think about the objects you actually deal with: a set of tags, a list of database rows, a range of numbers, a stream of characters in a string. None of them need to be a real array. If an object represents a collection of things, for..of is the natural way to walk it — you just have to teach the object how to hand out its items one at a time.

Arrays and strings already know how. This chapter shows you the machinery underneath so you can make your own objects work the same way.

Symbol.iterator

The fastest way to understand iterables is to build one. Take an object that clearly represents a collection but is not an array — the floors an elevator will stop at:

let floorRange = {
  bottom: 2,
  top: 6
};

// The goal: make this work
// for (let floor of floorRange) ... floor = 2, 3, 4, 5, 6

To make floorRange work with for..of, you add one method to it, keyed by the built-in symbol Symbol.iterator. The name is a symbol on purpose — it can never collide with a regular string property you might already have, like floorRange.iterator.

Here is the contract for..of follows, step by step:

  1. When the loop starts, it calls floorRange[Symbol.iterator]() exactly once. If that method is missing, for..of throws. The call must return an iterator — an object that has a next() method.
  2. From then on, for..of talks only to that returned iterator. The original object is out of the picture.
  3. Each time the loop needs a value, it calls iterator.next().
  4. The return value of next() must be an object shaped like { done: Boolean, value: any }. When done is true, the loop stops. Otherwise, value is the next item.
for..of— calls once →objSymbol.iteratorreturnsiterator
for..of— calls each pass →iterator.next(){ done: false, value: 2 }
…keeps calling until…{ done: true }→ loop ends
The Symbol.iterator protocol: for..of asks the object for an iterator once, then pulls values from that iterator until done is true.

Here is the full implementation, with the four steps marked in the code:

let floorRange = {
  bottom: 2,
  top: 6
};

// 1. for..of calls this method first
floorRange[Symbol.iterator] = function() {

  // ...and it returns the iterator object:
  // 2. From here on, for..of works only with this object, asking it for values
  return {
    current: this.bottom,
    last: this.top,

    // 3. next() is called on every iteration by for..of
    next() {
      // 4. it returns a value wrapped as { done, value }
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// now it works!
for (let floor of floorRange) {
  alert(floor); // 2, then 3, 4, 5, 6
}

Here is that exact floorRange running live. Change bottom and top, press the button, and watch for..of pull each value out of the iterator you defined:

interactiveLoop your own iterable with for..of

Notice what the design buys you: separation of concerns.

  • floorRange itself has no next() method.
  • A separate object — the iterator — is created by calling floorRange[Symbol.iterator](), and that object’s next() produces the values.

The iterator is a distinct thing from the collection it walks over. That separation is the whole point, and it matters more than it first looks.

Merging the object and its iterator

Technically you can fold the two roles together and let floorRange be its own iterator. It’s less code:

let floorRange = {
  bottom: 2,
  top: 6,

  [Symbol.iterator]() {
    this.current = this.bottom;
    return this;
  },

  next() {
    if (this.current <= this.top) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let floor of floorRange) {
  alert(floor); // 2, then 3, 4, 5, 6
}

Now floorRange[Symbol.iterator]() returns floorRange itself. It already has a next() method, and it tracks progress in this.current. Shorter, and for simple cases that’s fine.

The cost shows up when you want two loops over the same object at once. Both share a single current, because there’s only one iterator — the object. A nested for..of over the same floorRange would stomp on the outer loop’s position.

separate iterators — safeloop A → iterator{current:3}loop B → iterator{current:5}floorRange: unchanged
merged — shared stateloop A → floorRange.currentloop B → floorRange.currentboth write the same slot ⚠
Two iterators, two independent positions vs one shared iterator, one shared position.

Parallel loops over one object are rare, so the merged form is a reasonable default. Just know the tradeoff you’re making.

Strings are iterable

Arrays and strings are the two built-in iterables you’ll reach for constantly.

For a string, for..of walks over its characters:

for (let char of "code") {
  // runs 4 times, once per character
  alert(char); // c, then o, then d, then e
}

The important detail: it does this by character, not by UTF-16 code unit. That means surrogate pairs — characters that need two code units to encode, like many emoji and rarer scripts — come through as a single item:

let str = '🎯😎';
for (let char of str) {
  alert(char); // 🎯, and then 😎
}

Compare that with indexing by str[0], which would hand you back half of a surrogate pair — a broken fragment. The string iterator knows the encoding and does the right thing.

Calling an iterator by hand

for..of is the polite front door, but you can knock on the iterator directly. This does exactly what a for..of over the string would do, only spelled out:

let str = "Hello";

// same result as:
// for (let char of str) alert(char);

let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value); // outputs characters one by one
}

Pulling values by hand makes the pause obvious. Press “Call next()” once per character — the iterator remembers exactly where it left off between clicks, and hands back { done: true } when the string runs out:

interactiveDrive an iterator one next() at a time

You rarely need this. When you do, it’s because you want control that for..of doesn’t give you — chiefly, the ability to pause. You can pull a few values, go do something else, then come back and resume from where the iterator left off. for..of runs start to finish in one go; a manual iterator lets you interleave the iteration with other work.

Iterables and array-likes

Two terms sound alike and get mixed up constantly. Pin down the difference and a lot of confusing behavior stops being confusing.

  • Iterable — an object with a Symbol.iterator method. It can be looped with for..of.
  • Array-like — an object with numeric indexes and a length property. It looks shaped like an array.

These are independent properties. An object can be one, the other, both, or neither.

string “abc”iterablearray-like
floorRangeiterablenot array-like
{0,1,length}not iterablearray-like
Iterable and array-like are separate traits. Strings have both; floorRange has only one; a plain indexed object has the other.

The floorRange from earlier is iterable but not array-like: it has no indexed properties and no length.

Here’s the reverse — array-like but not iterable:

let arrayLike = { // has indexes and length => array-like
  0: "red",
  1: "green",
  length: 2
};

// Error: arrayLike is not iterable (no Symbol.iterator)
for (let item of arrayLike) {}

Both iterables and array-likes are usually not real arrays. They don’t have push, pop, map, or any of the array toolbox. That’s the practical annoyance: you’ve got something list-shaped and you’d love to run array methods on it. You need a way to turn it into a genuine array.

Array.from

Array.from is the universal converter. Hand it an iterable or an array-like, and it builds a real Array you can then treat like any other array.

Start with the array-like case:

let arrayLike = {
  0: "red",
  1: "green",
  length: 2
};

let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // green (a real array method works)

At line (*), Array.from inspects the object, decides whether it’s iterable or array-like, then copies every item into a new array.

Try it below. Type some comma-separated words — they get stored in a bare array-like object (numeric keys plus length, no array methods). Calling .map on it directly fails; Array.from first turns it into a genuine array, and then .map works:

interactiveArray.from turns an array-like into a real array

It works on iterables the same way:

// floorRange is the iterable object from earlier
let arr = Array.from(floorRange);
alert(arr); // 2,3,4,5,6 (array-to-string conversion)

The mapping argument

The full signature takes two optional extras:

Array.from(obj[, mapFn, thisArg])

mapFn runs on each element before it lands in the new array, and thisArg sets what this means inside mapFn. It’s map fused into the conversion, so you don’t build an intermediate array just to transform it.

// floorRange is the iterable from earlier

// square each number as it's copied
let arr = Array.from(floorRange, num => num * num);

alert(arr); // 4,9,16,25,36

A surrogate-safe way to split strings

Because Array.from uses the iterable behavior of a string, it splits by character — surrogate pairs stay intact:

let str = '🎯😎';

// split str into an array of characters
let chars = Array.from(str);

alert(chars[0]); // 🎯
alert(chars[1]); // 😎
alert(chars.length); // 2

That’s a real advantage over str.split(''), which cuts on UTF-16 code units and would break those two characters into four broken halves.

See both side by side. Type any string — mixing emoji or rare scripts with plain letters is the interesting case — and compare how each method chops it:

interactiveArray.from vs split('') on surrogate pairs

Under the hood, Array.from(str) is doing the same loop you’d write by hand:

let str = '🎯😎';

let chars = []; // Array.from does essentially this internally
for (let char of str) {
  chars.push(char);
}

alert(chars);

…just in one call.

You can build on this to write a slice that respects characters, which the native String.prototype.slice does not:

function slice(str, start, end) {
  return Array.from(str).slice(start, end).join('');
}

let str = '🎯😎🚀';

alert( slice(str, 1, 3) ); // 😎🚀

// the native method cuts by code unit
alert( str.slice(1, 3) ); // garbage (halves of two different surrogate pairs)

Summary

An object that works in for..of is iterable.

  • Technically, an iterable implements a method named Symbol.iterator.
    • Calling obj[Symbol.iterator]() returns an iterator — the object that drives the iteration.
    • An iterator has a next() method returning { done: Boolean, value: any }. When done is true, iteration is over; otherwise value is the next item.
  • for..of calls Symbol.iterator automatically, but you can call it yourself for finer control, including pausing and resuming.
  • Built-ins like strings and arrays implement Symbol.iterator.
  • The string iterator is surrogate-pair-aware, so it walks by character.

An array-like object has numeric indexes and a length. It may carry other properties and methods too, but it lacks the built-in array methods.

Iterable and array-like are separate traits — an object can be either, both, or neither. Look inside the language spec and you’ll find most built-in methods are written to accept iterables or array-likes rather than “real” arrays, because that’s the more general contract.

Array.from(obj[, mapFn, thisArg]) builds a genuine Array from any iterable or array-like obj, after which the full array toolbox is available. The optional mapFn and thisArg let you transform each item during the copy.