const myIterable = {
data: [1, 2, 3, 4, 5],
index: 0, // shared state β BUG!
[Symbol.iterator]() {
return {
next: () => {
if (this.index < this.data.length) {
return { value: this.data[this.index++], done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const n of myIterable) console.log(n); // 1 2 3 4 5
for (const n of myIterable) console.log(n); // nothing! index is already 5const myIterable = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
let index = 0; // fresh state per call β captured in closure
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const n of myIterable) console.log(n); // 1 2 3 4 5
for (const n of myIterable) console.log(n); // 1 2 3 4 5 βBug: index is stored on the iterable itself β shared across all iterations. After first loop finishes, index = 5. Second loop starts with index = 5 β immediately returns done.
Explanation: Each call to [Symbol.iterator]() must return a FRESH iterator with its own state. Storing index on the iterable object makes all iterations share state. Closure keeps index local per iterator.
Key Insight: An iterable is reusable; an iterator is single-use. [Symbol.iterator]() must create a new state (closure or object) each time β never shared state on the iterable itself.