JavaScript for…of Loop and Iterables Explained
The JavaScript for...of statement creates a loop
iterating over iterable objects, including built-in types like
Array, String, Map,
Set, and custom data structures. This article explores the
mechanics of how for...of interacts with the JavaScript
Iteration Protocols, detailing the role of Symbol.iterator,
the execution of the .next() method, and how the loop
handles completion and early termination.
The Iteration Protocol
To understand how for...of works, you must understand
the two protocols that govern iteration in JavaScript: the
Iterable Protocol and the Iterator
Protocol.
- Iterable Protocol: An object is iterable if it
defines a method at the key
[Symbol.iterator]. This method is a factory function that returns an iterator object. - Iterator Protocol: An iterator is an object that
implements a
next()method. Callingnext()returns an object with two properties:value: The current item in the sequence.done: A boolean indicating whether the sequence has finished (true) or has more values (false).
How for...of
Executes Under the Hood
When a for...of loop executes on a target object, it
performs the following steps automatically:
- Retrieves the Iterator: It checks for the
[Symbol.iterator]method on the target object. If the method does not exist, the engine throws aTypeError: [object] is not iterable. - Calls the Iterator Method: It invokes
target[Symbol.iterator]()to obtain a fresh iterator instance. - Calls
.next(): On each iteration cycle, the loop callsiterator.next(). - Evaluates
done:- If
doneisfalse, the loop assignsvalueto the loop variable and executes the code block. - If
doneistrue, the loop terminates immediately without executing the block for that step.
- If
Equivalent Manual Implementation
The behavior of for...of can be represented manually
using a while loop:
const collection = ['a', 'b', 'c'];
// How for...of works internally:
const iterator = collection[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
const item = result.value;
console.log(item); // Process the item
result = iterator.next();
}Working with Built-in Iterables
JavaScript provides built-in [Symbol.iterator]
implementations for several core objects:
- Arrays & TypedArrays: Yield each array element in index order.
- Strings: Yield individual Unicode characters (correctly handling surrogate pairs).
- Maps: Yield
[key, value]pairs by default (map.entries()), or keys/values viamap.keys()andmap.values(). - Sets: Yield unique values in insertion order.
- NodeLists &
arguments: Array-like DOM collections and function argument objects come with built-in iterators.
Note: Plain JavaScript objects ({}) are not iterable
by default because they do not implement
[Symbol.iterator].
Creating Custom Iterables
You can make any custom object compatible with for...of
by implementing the [Symbol.iterator] method.
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
};
for (const num of range) {
console.log(num); // Logs: 1, then 2, then 3
}Early Termination and
Cleanup with return()
If a for...of loop exits early—via a break,
return, or an uncaught exception—the loop checks if the
iterator object has a return() method.
If return() is present, for...of
automatically invokes it before exiting. This allows iterators to
perform cleanup operations, such as closing file handles, freeing
memory, or closing network streams.
const cleanableIterable = {
[Symbol.iterator]() {
return {
next() {
return { value: 'data', done: false };
},
return() {
console.log('Cleanup logic executed.');
return { done: true };
}
};
}
};
for (const item of cleanableIterable) {
console.log(item);
break; // Triggers iterator.return()
}
// Output:
// 'data'
// 'Cleanup logic executed.'Key Differences:
for...of vs. for...in
for...of: Uses the iterable protocol to loop over values produced by the object’s iterator. It ignores non-iterable properties and prototype keys.for...in: Loops over all enumerable property names (keys) of an object, including inherited properties from its prototype chain.