HardModern JS🐛 Debug Challenge

Iterator is single-use — second loop produces nothing

Buggy Code — Can you spot the issue?

const range = {
  data: [1, 2, 3],
  index: 0,
  [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 range) console.log(n);
for (const n of range) console.log(n);

Fixed Code

const range = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    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 range) console.log(n);
for (const n of range) console.log(n);

Bug Explained

Bug: index is stored on the range object itself — shared across all iterations. After the first loop, index = 3. The second loop starts with index = 3 and immediately gets done: true.

Explanation: index is now a local variable in the [Symbol.iterator]() call — a fresh closure per iteration. Each for...of gets a brand-new independent iterator.

Key Insight: An iterable must return a FRESH iterator each time [Symbol.iterator]() is called. Never store iterator state on the iterable itself.

More Modern JS Debug Challenges

EasyManual swap loses original valueEasysort() mutates the original arrayMediumRegex with global flag alternates true/falseMediumCustom error class not extending Error

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab