JavaScript Iterators: How They Work and How to Use Them

JavaScript iterators are the low-level objects that let you step through a sequence of values one item at a time. They power features like for...of, the spread operator, array destructuring, and many built-in data structures.

Quick answer: An iterator is an object with a next() method that returns { value, done }. You usually do not build one manually unless you need custom iteration behavior; most of the time you use iterable objects like arrays, strings, maps, sets, or generator functions.

Difficulty: Beginner

You'll understand this better if you know: basic JavaScript objects, arrays, and how for...of loops read values from a collection.

Browser support: Newly available since Mar 2025 (and more below)

1. What Is a JavaScript Iterator?

A JavaScript iterator is an object that returns the next value in a sequence each time you ask for it. It follows a simple protocol: call next(), get back an object with a value and a done property, and repeat until done becomes true.

  • next() advances through the sequence.
  • value is the current item.
  • done tells you whether the iterator is finished.
  • Iterators are usually created by iterable objects such as arrays, strings, maps, sets, and custom objects.

An important distinction is that an iterator is not the same thing as an iterable. An iterable is a value that can produce an iterator, usually through Symbol.iterator.

2. Why JavaScript Iterators Matter

Iterators matter because they give JavaScript a common way to read sequences, whether the data comes from an array, a map, a set, a string, or your own custom type. They make loops and other language features work consistently across many kinds of data.

They are especially useful when you want to:

  • process values one at a time instead of all at once,
  • build custom collection types that work with for...of,
  • create lazy or on-demand sequences,
  • read data from APIs or generators in a controlled way.

For most beginners, the biggest benefit is practical: you already use iterators indirectly whenever you loop over modern JavaScript collections.

3. Basic Syntax

The simplest iterator has a next() method. Each call returns an IteratorResult object with the shape { value: ..., done: ... }.

Minimal iterator example

This example shows the core idea without involving any loop syntax yet.

JavaScript
const iterator = {
  next() {
    return { value: 1, done: false };
  }
};

const result = iterator.next();

console.log(result);
// { value: 1, done: false }
Output✓ Tested · Node 24
{ value: 1, done: false }

This iterator always returns the same result, so it is not very useful yet. Real iterators usually change internal state each time next() is called.

Iterator with internal state

Here the iterator moves through a small sequence and stops when it reaches the end.

JavaScript
const iterator = {
  current: 0,
  next() {
    if (this.current < 3) {
      return { value: this.current++, done: false };
    }

    return { value: undefined, done: true };
  }
};

Each call advances current until the sequence is exhausted. After that, the iterator signals completion with done: true.

4. Step-by-Step Examples

Example 1: Reading values manually

You can call next() yourself to see exactly how iteration works.

JavaScript
const letters = ["a", "b", "c"];
const iterator = letters[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
Output✓ Tested · Node 24
{ value: "a", done: false }
{ value: "b", done: false }
{ value: "c", done: false }
{ value: undefined, done: true }

The array provides an iterator through Symbol.iterator. Each call returns the next array item until the sequence ends.

Example 2: Using an iterator with a for...of loop

Most of the time, you do not call next() yourself. JavaScript does it for you inside for...of.

JavaScript
const numbers = [10, 20, 30];

for (const number of numbers) {
  console.log(number);
}
Output✓ Tested · Node 24
10
20
30

This works because arrays are iterable, and iterable objects can produce an iterator behind the scenes.

Example 3: Checking the iterator result shape

Iterators always return a result object with the same basic structure, even when they are done.

JavaScript
const iterator = ["x"][Symbol.iterator]();

const first = iterator.next();
const second = iterator.next();

console.log(first);
console.log(second);
Output✓ Tested · Node 24
{ value: "x", done: false }
{ value: undefined, done: true }

The first call returns the value "x". The second call indicates that iteration is finished.

Example 4: A custom iterator for a range

This is a realistic pattern when you want an object to behave like a sequence.

JavaScript
const range = {
  start: 1,
  end: 3,
  [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 value of range) {
  console.log(value);
}
Output✓ Tested · Node 24
1
2
3

This object becomes iterable because it defines Symbol.iterator and returns an iterator object.

5. Practical Use Cases

  • Looping through arrays, strings, maps, and sets with for...of.
  • Creating custom collections such as ranges, queues, or tree traversal helpers.
  • Making object-like data usable with spread syntax, such as [...myCollection].
  • Building lazy data pipelines where values are generated only when needed.
  • Writing reusable utilities that work with any iterable input, not just arrays.

Iterators are also useful when you want to separate the idea of how to produce values from how to consume them. That separation makes code more flexible and easier to reuse.

6. Common Mistakes

Mistake 1: Confusing an iterator with an iterable

Beginners often assume any object that “can be looped over” is itself an iterator. In JavaScript, the iterable produces the iterator; the iterator actually returns values.

Problem: This code tries to use next() on an array directly, but arrays do not have a next() method.

JavaScript
const items = ["a", "b"];

console.log(items.next());
Output✓ Tested · Node 24
Uncaught TypeError: items.next is not a function

Fix: Ask the array for its iterator first by calling Symbol.iterator.

JavaScript
const items = ["a", "b"];
const iterator = items[Symbol.iterator]();

console.log(iterator.next());
Output✓ Tested · Node 24
{ value: "a", done: false }

The fixed version works because Symbol.iterator returns the iterator object for the array.

Mistake 2: Returning the wrong shape from next()

Iterator results must have the expected { value, done } shape. If done is missing or spelled incorrectly, consuming code may behave unexpectedly.

Problem: This custom iterator returns a plain value instead of an iterator result object, so consumers like for...of cannot use it correctly.

JavaScript
const badIterable = {
  [Symbol.iterator]() {
    return {
      next() {
        return 1;
      }
    };
  }
};

for (const value of badIterable) {
  console.log(value);
}
Output✓ Tested · Node 24
Uncaught TypeError: Iterator result 1 is not an object

Fix: Return an object with both value and done.

JavaScript
const goodIterable = {
  [Symbol.iterator]() {
    let count = 0;

    return {
      next() {
        if (count < 1) {
          return { value: count++, done: false };
        }

        return { value: undefined, done: true };
      }
    };
  }
};

The corrected iterator works because consuming code can recognize when the sequence ends.

Mistake 3: Reusing an exhausted iterator

Iterators are stateful and usually one-time-use. Once an iterator is done, calling next() again keeps returning done: true.

Problem: This code expects the same iterator to start over, but iterators do not reset automatically.

JavaScript
const iterator = [1, 2][Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// Still exhausted
Output✓ Tested · Node 24
{ value: 1, done: false }
{ value: 2, done: false }
{ value: undefined, done: true }

Fix: Create a fresh iterator when you need to iterate again, or use the iterable again instead of storing the iterator.

JavaScript
const items = [1, 2];

for (const value of items) {
  console.log(value);
}

for (const value of items) {
  console.log(value);
}
Output✓ Tested · Node 24
1
2
1
2

The fixed version works because each loop asks the array for a new iterator internally.

7. Best Practices

Practice 1: Use iterables, not raw iterators, in most code

If your goal is simply to loop over data, prefer an iterable like an array, set, or custom iterable object. That gives you reusable iteration instead of a one-time cursor.

JavaScript
const numbers = [1, 2, 3];

for (const number of numbers) {
  console.log(number);
}
Output✓ Tested · Node 24
1
2
3

This is better than storing a single iterator because the iterable can be consumed multiple times.

Practice 2: Keep iterator state private

Custom iterators are easier to reason about when their moving parts are hidden inside the iterator factory, not exposed as public fields.

JavaScript
const counter = {
  [Symbol.iterator]() {
    let value = 0;

    return {
      next() {
        if (value >= 3) {
          return { value: undefined, done: true };
        }

        return { value: value++, done: false };
      }
    };
  }
};

Private state helps prevent accidental mutation from the outside and keeps the iteration behavior predictable.

Practice 3: Return a fresh iterator each time

If an object is iterable, its Symbol.iterator method should usually create a new iterator for each call.

JavaScript
const bag = {
  items: ["pen", "notebook"],
  [Symbol.iterator]() {
    let index = 0;
    const items = this.items;

    return {
      next() {
        if (index < items.length) {
          return { value: items[index++], done: false };
        }

        return { value: undefined, done: true };
      }
    };
  }
};

Each iteration gets its own cursor, so the object can be looped over more than once.

8. Limitations and Edge Cases

  • Iterators are usually one-way and stateful. Once consumed, they do not reset unless you create a new one.
  • An iterator alone is not enough for for...of. The object also needs to be iterable through Symbol.iterator.
  • Not every loop in JavaScript uses iterators. Traditional for and while loops do not require the iterator protocol.
  • Some array methods such as map, filter, and reduce do not consume iterators directly; they operate on arrays.
  • If a custom iterator throws an error during next(), iteration stops immediately and the error propagates to the caller.
  • In browser and Node.js environments, strings, arrays, maps, and sets are iterable, but plain objects are not iterable by default.

A common error message is TypeError: object is not iterable. This usually means you passed a non-iterable value to something that expects an iterable, such as spread syntax, for...of, or array destructuring.

9. Practical Mini Project

Let’s build a small custom iterable that yields the first few even numbers. This example brings together the iterator protocol, Symbol.iterator, and for...of.

JavaScript
const evenNumbers = {
  limit: 5,
  [Symbol.iterator]() {
    let count = 0;
    let current = 0;
    const limit = this.limit;

    return {
      next() {
        if (count >= limit) {
          return { value: undefined, done: true };
        }

        const value = current;
        current += 2;
        count++;

        return { value: value, done: false };
      }
    };
  }
};

for (const number of evenNumbers) {
  console.log(number);
}
Output✓ Tested · Node 24
0
2
4
6
8

This iterable yields 0, 2, 4, 6, 8. It is small, self-contained, and works naturally with any code that expects an iterable.

10. Key Points

  • An iterator is an object with a next() method.
  • next() returns { value, done }.
  • An iterable is a value that can create an iterator, usually through Symbol.iterator.
  • for...of, spread syntax, and destructuring use iterators behind the scenes.
  • Custom iterators are useful when built-in collections do not match your data shape.
  • Iterators are stateful and usually one-time-use.

11. Practice Exercise

Build a custom iterable that generates the first four squares: 1, 4, 9, 16.

  • Create an object with a Symbol.iterator method.
  • Make the iterator return one square at a time.
  • Use for...of to print the values.

Expected output:

JavaScript
1
4
9
16

Hint: Keep a counter inside the iterator and multiply it by itself before returning each value.

Show solution
JavaScript
const squares = {
  [Symbol.iterator]() {
    let n = 1;

    return {
      next() {
        if (n > 4) {
          return { value: undefined, done: true };
        }

        const value = n * n;
        n++;

        return { value: value, done: false };
      }
    };
  }
};

for (const value of squares) {
  console.log(value);
}
Output✓ Tested · Node 24
1
4
9
16

Browser Support

FeatureChromeEdgeFirefoxSafariSafari iOS
Iterator methods
Newly available since Mar 2025
122+122+131+18.4+18.4+
Array iterators
Widely available since Nov 2020
66+14+60+10+10+

Minimum versions with full support. Data from web-features 3.43.0 (the data behind MDN's Baseline), updated at every site build.

12. Final Summary

JavaScript iterators are the mechanism that lets you step through values one at a time. They are simple at the protocol level: call next(), read value, and stop when done becomes true. What makes them powerful is that they sit underneath many everyday language features, including for...of, spread syntax, and destructuring.

In practice, you will usually work with iterables rather than raw iterators. Arrays, strings, maps, and sets already support iteration, and custom iterable objects let you define your own sequences when needed. Once you understand the difference between an iterable and an iterator, the rest of JavaScript’s iteration model becomes much easier to use correctly.

Next, try writing a custom iterable that yields file names, quiz questions, or numbers from a business rule. The more you practice with Symbol.iterator and next(), the more natural iterator-based code will feel.