← Back to Chapters

JavaScript Iterators & Generators

⚙️ JavaScript Iterators & Generators

⚡ Quick Overview

JavaScript iterators and generators are tools for working with sequences of values. Iterators provide a standard way to access items one-by-one, while generators make it easier to create custom iterators using special functions that can pause and resume.

Together, they help you build lazy sequences, infinite streams, and readable iteration logic for arrays, custom objects, and more.

? Key Concepts

  • Iterator: An object with a next() method that returns { value, done }.
  • Iterable: An object that defines [Symbol.iterator]() and can be used in for...of.
  • Generator function: Declared with function* and uses yield to produce values.
  • Yield: Pauses the generator and outputs a value; execution resumes from the last yield.
  • All generators are iterators, but not all iterators are generators.
  • Lazy evaluation: Values are produced only when requested (on each next() call).

? Syntax / Theory

? Iterator Protocol

An object is an iterator if it has a next() method that returns an object with:

  • value: The current value.
  • done: false if there are more values, true when iteration is finished.

? Generator Functions

A generator is created by calling a function*. Inside it, you use yield to send values out one by one:

  • yield x; pauses execution and outputs x.
  • return x; ends the generator and sets the final value when done: true.
  • yield* delegates to another generator or iterable.

? Code Examples

? Basic Array Iterator

Arrays are built-in iterables. You can get their iterator using [Symbol.iterator]().

? View Code Example
const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

? Simple Generator Function

A generator can produce a sequence of values with yield:

? View Code Example
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

♾️ Infinite Sequence with Generators

Generators are ideal for infinite or very large sequences because values are generated on demand.

? View Code Example
function* infiniteNumbers() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

const numbers = infiniteNumbers();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2

? Live Output & Explanation

? Understanding the next() Results

Iterator example:

  • First call: { value: 10, done: false } → first array element, not finished.
  • Second call: { value: 20, done: false } → second element.
  • Third call: { value: 30, done: false } → third element.
  • Fourth call: { value: undefined, done: true } → no more elements; iteration completed.

Generator example:

  • Each gen.next() runs the generator until the next yield.
  • Values 1, 2, and 3 are yielded one by one.
  • When there are no more yield statements, done becomes true.

Infinite generator:

  • Each call to numbers.next().value gives the next integer: 0, 1, 2, and so on.
  • Because the loop is while (true), done never becomes true unless you stop using it.

? Use Cases

  • Creating lazy sequences (e.g., pages of API results, large datasets).
  • Implementing custom iteration over complex data structures.
  • Generating infinite or long-running streams (IDs, timestamps, counters).
  • Controlling asynchronous workflows when combined with promises (in advanced patterns).

? Tips & Best Practices

  • Use generators to simplify complex iteration logic and avoid manually tracking indexes or state.
  • Make custom objects iterable by defining [Symbol.iterator]() and returning an iterator or generator.
  • Use lazy sequences for large datasets so you do not keep everything in memory at once.
  • Prefer descriptive generator names like range, take, or filterValues for readability.
  • Use yield* to delegate to another generator or iterable and avoid nested loops.

? Try It Yourself

  • Write a generator function* squares() that yields square numbers 1, 4, 9, 16, 25 and then finishes.
  • Create a custom iterable object that uses [Symbol.iterator] to iterate over only even numbers in an array.
  • Define two generators, e.g. evens() and odds(), then create a third generator function* numbers() that uses yield* to delegate to both.
  • Build an infinite ID generator that yields strings like "ID-1", "ID-2", and so on.