Iterable Protocols

The Course
•
4 min read

An iterable supplies a method at Symbol.iterator. Calling that method produces an iterator with next(), whose result is an object containing value and done. for/of uses this protocol for you. An array is iterable; the iterator created from it holds a current position.

Symbols are unique property keys. Symbol.iterator is a shared well-known symbol that JavaScript recognizes as a protocol hook. It does not mean an ordinary property named “iterator”. You usually consume existing iterables or write generators rather than implementing every protocol manually.

Two iterators from one array progress independently. One iterator can be exhausted and then keeps reporting done. An iterable is not necessarily reusable: a generator object is itself a stateful iterator, so iterating it again does not restart it.

The task pool will accept a synchronous iterable and pull values only when a worker slot is available. This makes demand visible, but returning every result still requires memory proportional to the number of results.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Each editor is independent. Async examples end with main() so this editor waits for their work.

Predict, then run JavaScript
const values = [10, 20];
const first = values[Symbol.iterator]();
const second = values[Symbol.iterator]();

console.log(first.next().value);
console.log(first.next().value);
console.log(second.next().value);
console.log(first.next().done);
Check your prediction
10
20
10
true

One iterable can produce independent cursors

Iterable and iterator are roles, not interchangeable names for a collection. The iterable can create an iterator; the iterator tracks progress and returns result records. A next result describes both whether a value was produced and whether iteration has finished.

An iterator may be the iterable itself, as with a generator object, so a second loop can find it already exhausted. Reuse requires creating a fresh iterator or calling the generator function again, not merely putting the same stateful object in another for-of loop.

One iterable can produce independent cursors: trace JavaScript
const values = ["a", "b"];
const cursor = values[Symbol.iterator]();

console.log(cursor.next().done);
console.log(cursor.next().value);
console.log(cursor.next().done);
console.log(cursor.next().done);
Check your prediction
false
b
true
true

The first call consumed a even though it only printed done. Reading one field of the result does not undo the advancement.

One iterable can produce independent cursors: contrast JavaScript
const values = ["a", "b"];
const a = values[Symbol.iterator]();
const b = values[Symbol.iterator]();

a.next();
console.log(a.next().value);
console.log(b.next().value);
Check your prediction
b
a

Each iterator owns its position. The array data is shared, but the cursors are not one shared progress counter.

Try the rule on new inputs

Collect every next result for an empty and a one-item array. Mark which call consumes a value. Then explain why collecting all results from an infinite iterable cannot finish.

Your turn

Exercise

Iterable Protocols: practise

Create an iterator over [“a”, “b”]. Read both values and then print true for its exhausted done flag. Create a fresh iterator and show that it starts at a.

Your workspace JavaScript
// Call Symbol.iterator to create a fresh cursor each time.
Hint 1: the idea

The iterable and the iterator are separate roles.

Hint 2: a first step

Store the first iterator before calling next repeatedly.

Show one solution
const values = ["a", "b"];
const iterator = values[Symbol.iterator]();

console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().done);
console.log(values[Symbol.iterator]().next().value);
a
b
true
a

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Inspect Object.keys(values) and explain why it does not show Symbol.iterator. Test an empty list. Then write down why an infinite iterable would be unsuitable for a helper that collects all results before returning.

Was this article helpful?

Your response is saved on this device.