Symbol & the Iterator Protocol
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). TypeScript and ESLint can detect when a custom collection class lacks Symbol.iterator implementation, but the detection is not automatic—it requires a rule specifically configured to check that custom data structures are iterable. The common mistakes (stateful iterator bugs, infinite loops, async mismatches) are not caught by default linting; they require runtime testing or careful code review to surface.
Closest to 'simple parameterised fix' (e3). The quick_fix states 'implement Symbol.iterator on custom objects'—this is a straightforward pattern to add to a single class. However, if the error involves resetting state or switching to Symbol.asyncIterator for async sources, the fix touches the iterator pattern in multiple places within that class, pushing slightly toward e5 in some cases. Typical fix is localised to one component.
Closest to 'persistent productivity tax' (b5). The Iterator Protocol is pervasive across JavaScript iteration syntax (for...of, spread operator, destructuring). If a codebase adopts this pattern inconsistently—some custom data structures iterable, others not—maintainers must remember which structures support iteration and which require manual loops. The protocol's reach is broad (it integrates with language syntax), and deciding how to implement it shapes how future data structures are designed.
Closest to 'serious trap' (t7). The misconception field directly states the trap: 'Symbol.iterator is only for arrays.' Beyond that, the common mistakes reveal deep gotchas: returning a new iterator on each call breaks for...of reuse; forgetting done: true causes infinite loops; confusing Symbol.iterator with Symbol.asyncIterator contradicts async iteration expectations. These traps contradict what developers may assume from experience with arrays or generators, making them serious cognitive pitfalls.
Also Known As
TL;DR
Explanation
The iterator protocol: an object is iterable if it has a [Symbol.iterator]() method returning an iterator (an object with a next() method returning {value, done}). Implementing this makes custom objects work with: for...of, spread operator, destructuring, Array.from(), and yield*. Generator functions automatically produce iterables. Use cases: paginated API responses (each iteration fetches next page), infinite sequences, lazy transformations, and custom collection classes.
Common Misconception
Why It Matters
Common Mistakes
- Returning a new iterator on each call to [Symbol.iterator] without resetting state — broken for...of after first use.
- Forgetting to set done: true — infinite iterator without termination.
- Using a generator function where a manual iterator would be clearer.
- Not implementing Symbol.asyncIterator for async data sources — for await...of requires this.
Code Examples
// Custom range — no iterator, cannot use for...of:
class Range {
constructor(start, end) { this.start = start; this.end = end; }
// Cannot do: for (const n of new Range(1, 5)) {}
// Cannot do: [...new Range(1, 5)]
}
// Range with Symbol.iterator:
class Range {
constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
}
// Now works everywhere:
for (const n of new Range(1, 5)) console.log(n); // 1 2 3 4 5
const arr = [...new Range(1, 5)]; // [1,2,3,4,5]
const [a, b, c] = new Range(10, 20); // 10, 11, 12