Give a collection a uniform way to be walked through, without exposing how it stores its elements internally.
If callers loop over a custom collection using `collection.items[i]`, the collection can never change its internal storage (say, from an array to a linked list) without breaking every caller that assumed array indexing.
The collection exposes a method that returns an iterator — an object with `next()` that tracks position on its own. Client code calls `next()` repeatedly and never touches the collection's internal structure.
Because `Range` implements the iterable protocol, it works directly with `for...of` — the internal storage stays private.
class Range {
constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current < end) return { value: current++, done: false };
return { value: undefined, done: true };
},
};
}
}
for (const n of new Range(1, 5)) {
console.log(n); // 1 2 3 4
}
console.log([...new Range(0, 3)]); // [0, 1, 2]