JavaScript - JavaScript Generators and Iterators

1. Introduction

JavaScript provides several ways to repeat operations and process collections of data. The most common methods are loops such as for, while, and for...of. However, sometimes we need more control over how values are produced, when execution pauses, and how data is accessed one item at a time.

Generators and iterators are advanced JavaScript features that help us achieve this.

An iterator allows us to access values one after another. A generator is a special function that can pause its execution and continue later, producing a sequence of values as needed.

These concepts are useful in:

  • Processing large collections of data.

  • Creating custom sequences.

  • Reading data one item at a time.

  • Controlling the execution of a function.

  • Working with JavaScript's iterable objects.

  • Building custom data structures and data-processing systems.

2. What Is an Iterator?

An iterator is an object that provides a way to access elements of a collection sequentially, one value at a time.

Instead of returning all values at once, an iterator returns an object containing the next value and information about whether the iteration has finished.

An iterator follows the next() method convention.

Basic iterator structure

JavaScript

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

console.log(iterator.next());

Output

JavaScript

{ value: 10, done: false }

The object returned by next() contains two important properties:

|
Property

|

Meaning

|
| --- | --- |
|

value

|

The current value produced by the iterator.

|
|

done

|

Indicates whether the iterator has finished producing values.

|

When the iterator has no more values, it returns:

JavaScript

{
    value: undefined,
    done: true
}

The iterator protocol requires a next() method that returns an object with these properties.

3. Understanding the next() Method

The next() method is used to request the next value from an iterator.

Consider the following example:

JavaScript

const iterator = {
    count: 1,

    next() {
        if (this.count <= 3) {
            return {
                value: this.count++,
                done: false
            };
        }

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

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Output

JavaScript

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

Explanation

  1. The first call returns 1.

  2. The second call returns 2.

  3. The third call returns 3.

  4. The fourth call indicates that the iteration is complete.

The iterator maintains its state between calls. This means it remembers where it stopped and produces the next value when requested.

4. What Is an Iterable?

An iterable is an object that can provide an iterator through the Symbol.iterator method.

JavaScript's for...of loop works with iterable objects.

Common built-in iterables include:

  • Arrays.

  • Strings.

  • Maps.

  • Sets.

  • Typed arrays.

Example: Array as an iterable

JavaScript

const numbers = [10, 20, 30];

for (const number of numbers) {
    console.log(number);
}

Output

10
20
30

The array is iterable, so JavaScript can obtain its iterator and retrieve values one at a time.

Example: Using an array iterator directly

JavaScript

const numbers = [10, 20, 30];

const iterator = numbers[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Output

JavaScript

{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }

Here, Symbol.iterator provides the iterator for the array.

5. What Is a Generator?

A generator is a special type of JavaScript function that can pause its execution and resume it later.

A generator function is declared using an asterisk (*) after the function keyword.

Syntax

JavaScript

function* generatorName() {
    // Generator statements
}

A generator function does not execute its body immediately when it is called. Instead, it returns a generator object.

Example

JavaScript

function* numbers() {
    yield 10;
    yield 20;
    yield 30;
}

const generator = numbers();

console.log(generator);

The call to numbers() creates a generator object. The statements inside the generator begin executing when next() is called.

6. The yield Keyword

The yield keyword is used inside a generator function to produce a value and pause execution.

When a generator reaches yield:

  1. It returns the yielded value.

  2. It pauses at that point.

  3. It remembers its execution state.

  4. It resumes when next() is called again.

Example

JavaScript

function* numbers() {
    yield 10;
    yield 20;
    yield 30;
}

const generator = numbers();

console.log(generator.next());
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

Output

JavaScript

{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }

Detailed explanation

When the generator is created:

JavaScript

const generator = numbers();

The function body has not yet started executing.

When the first next() is called:

JavaScript

generator.next();

The generator starts running and reaches:

JavaScript

yield 10;

It returns:

JavaScript

{ value: 10, done: false }

The generator pauses at the first yield.

When next() is called again, execution resumes after yield 10 and reaches:

JavaScript

yield 20;

It returns 20 and pauses again.

The same process occurs for 30.

After the final value has been yielded, another next() call returns done: true.

7. Difference Between return and yield

The yield and return keywords behave differently inside a generator.

|
yield

|

return

|
| --- | --- |
|

Produces a value and pauses execution.

|

Completes the generator immediately.

|
|

The generator can resume afterward.

|

The generator does not continue normally after the return.

|
|

Multiple yield statements can produce multiple values.

|

A return provides the final result.

|
|

The yielded result has done: false.

|

The returned result has done: true.

|

Example using yield

JavaScript

function* example() {
    yield 10;
    yield 20;
    yield 30;
}

const generator = example();

console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

Output

JavaScript

{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }

Example using return

JavaScript

function* example() {
    yield 10;
    return 20;
    yield 30;
}

const generator = example();

console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

Output

JavaScript

{ value: 10, done: false }
{ value: 20, done: true }
{ value: undefined, done: true }

The yield 30 statement is never reached because the return ends the generator.

8. Generator Execution Flow

The following example shows how a generator pauses and resumes.

JavaScript

function* processData() {
    console.log("Step 1");
    yield "First value";

    console.log("Step 2");
    yield "Second value";

    console.log("Step 3");
    yield "Third value";

    console.log("Completed");
}

const generator = processData();

console.log("Before execution");

console.log(generator.next());

console.log("Between calls");

console.log(generator.next());

console.log(generator.next());

console.log(generator.next());

Output

Before execution
Step 1
{ value: 'First value', done: false }
Between calls
Step 2
{ value: 'Second value', done: false }
Step 3
{ value: 'Third value', done: false }
Completed
{ value: undefined, done: true }

Explanation

The generator does not run all statements at once.

During the first next() call:

Step 1

is printed, and execution pauses at the first yield.

During the second next() call:

Step 2

is printed, and execution pauses at the second yield.

During the third next() call:

Step 3

is printed, and execution pauses at the third yield.

During the fourth next() call, the generator finishes.

This behavior makes generators useful for controlled execution.

9. Using Generators with for...of

Generators are iterable because their generator objects provide the iterator protocol.

Therefore, a generator can be used with a for...of loop.

Example

JavaScript

function* numbers() {
    yield 10;
    yield 20;
    yield 30;
}

for (const number of numbers()) {
    console.log(number);
}

Output

10
20
30

The for...of loop automatically calls next() until the iterator is finished.

This is more convenient than manually calling next().

Equivalent manual process

JavaScript

const generator = numbers();

let result = generator.next();

while (!result.done) {
    console.log(result.value);
    result = generator.next();
}

Both examples produce the same values.

10. Creating a Custom Iterator with a Generator

Generators make it easier to create custom iterators without manually writing the next() method.

Example: Counting numbers

JavaScript

function* countNumbers(limit) {
    for (let i = 1; i <= limit; i++) {
        yield i;
    }
}

const numbers = countNumbers(5);

for (const number of numbers) {
    console.log(number);
}

Output

1
2
3
4
5

Explanation

The generator uses a loop to produce numbers from 1 to 5.

Each time the generator reaches yield i, it pauses and returns the current number.

When the for...of loop requests the next value, the generator resumes from where it paused.

This provides a simple way to create a sequence of values.

11. Passing Values into a Generator

The next() method can also accept an argument.

That argument becomes the result of the previous yield expression when the generator resumes.

Example

JavaScript

function* calculator() {
    const first = yield "Enter the first number";
    const second = yield "Enter the second number";

    return first + second;
}

const generator = calculator();

console.log(generator.next());
console.log(generator.next(10));
console.log(generator.next(20));

Output

JavaScript

{ value: 'Enter the first number', done: false }
{ value: 'Enter the second number', done: false }
{ value: 30, done: true }

Detailed explanation

The first call:

JavaScript

generator.next();

starts the generator and reaches:

JavaScript

yield "Enter the first number";

The generator pauses.

The second call:

JavaScript

generator.next(10);

resumes the generator. The value 10 becomes the result of the first yield expression:

JavaScript

const first = 10;

The generator then reaches the second yield and pauses.

The third call:

JavaScript

generator.next(20);

assigns 20 to second.

The generator returns:

JavaScript

first + second

which is 30.

This technique allows values to be passed into a generator during execution.

12. Generator Delegation Using yield*

The yield* keyword is used to delegate to another iterable or generator.

It allows one generator to produce values from another generator.

Example

JavaScript

function* first() {
    yield 1;
    yield 2;
}

function* second() {
    yield* first();
    yield 3;
    yield 4;
}

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

Output

1
2
3
4

Explanation

The second() generator contains:

JavaScript

yield* first();

This tells the generator to yield all values produced by first() before continuing.

The execution sequence is:

  1. first() yields 1.

  2. first() yields 2.

  3. first() finishes.

  4. second() continues and yields 3.

  5. second() yields 4.

Generator delegation is useful when combining multiple sequences.

13. Infinite Generators

A generator can produce an unlimited sequence of values.

This is possible because a generator pauses at each yield and does not need to finish the entire sequence immediately.

Example

JavaScript

function* infiniteNumbers() {
    let number = 1;

    while (true) {
        yield number++;
    }
}

const numbers = infiniteNumbers();

console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);

Output

1
2
3
4

The generator can continue producing values indefinitely.

However, using an infinite generator with a normal for...of loop without a stopping condition can result in an endless loop.

Example with a stopping condition

JavaScript

function* infiniteNumbers() {
    let number = 1;

    while (true) {
        yield number++;
    }
}

for (const number of infiniteNumbers()) {
    if (number > 5) {
        break;
    }

    console.log(number);
}

Output

1
2
3
4
5

The break statement stops the loop before it continues indefinitely.

14. Generator Methods: next(), return(), and throw()

A generator object provides three important methods.

14.1 next()

The next() method resumes the generator and retrieves the next result.

JavaScript

function* example() {
    yield 10;
    yield 20;
}

const generator = example();

console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

Output:

JavaScript

{ value: 10, done: false }
{ value: 20, done: false }
{ value: undefined, done: true }

14.2 return()

The return() method terminates the generator and provides a final value.

JavaScript

function* example() {
    yield 10;
    yield 20;
    yield 30;
}

const generator = example();

console.log(generator.next());
console.log(generator.return("Finished"));
console.log(generator.next());

Output:

JavaScript

{ value: 10, done: false }
{ value: 'Finished', done: true }
{ value: undefined, done: true }

The generator is closed after return().

14.3 throw()

The throw() method throws an error at the current paused location of the generator.

JavaScript

function* example() {
    try {
        yield 10;
        yield 20;
    } catch (error) {
        console.log("Error handled:", error.message);
    }
}

const generator = example();

console.log(generator.next());

console.log(generator.throw(new Error("Something went wrong")));

Output:

{ value: 10, done: false }
Error handled: Something went wrong
{ value: undefined, done: true }

The error is caught by the generator's catch block.

15. Practical Example: Generating Student Roll Numbers

Suppose a school needs to generate roll numbers one at a time.

A generator can produce the roll numbers without creating a complete array in advance.

Example

JavaScript

function* generateRollNumbers(start, end) {
    for (let roll = start; roll <= end; roll++) {
        yield roll;
    }
}

const rollNumbers = generateRollNumbers(101, 105);

for (const rollNumber of rollNumbers) {
    console.log("Student Roll Number:", rollNumber);
}

Output

Student Roll Number: 101
Student Roll Number: 102
Student Roll Number: 103
Student Roll Number: 104
Student Roll Number: 105

Advantages

  • Values are generated when needed.

  • The generator maintains the current roll number.

  • The complete sequence does not have to be stored in an array.

  • It can be stopped early if required.

16. Practical Example: Processing Large Data

Consider a situation where an application needs to process a large number of records.

Creating a large array of all records may require considerable memory. A generator can produce records one at a time.

Example

JavaScript

function* generateRecords(total) {
    for (let i = 1; i <= total; i++) {
        yield {
            id: i,
            name: "Student " + i
        };
    }
}

const records = generateRecords(3);

for (const record of records) {
    console.log(record);
}

Output

JavaScript

{ id: 1, name: 'Student 1' }
{ id: 2, name: 'Student 2' }
{ id: 3, name: 'Student 3' }

The generator creates each record as the loop requests it.

Important point

Generators are useful for lazy data production, but they do not automatically make every operation memory-efficient. If the underlying data is already loaded into memory, the generator may simply provide another way to access it.

17. Difference Between Normal Functions and Generator Functions

|
Normal Function

|

Generator Function

|
| --- | --- |
|

Declared using function.

|

Declared using function*.

|
|

Executes normally when called.

|

Returns a generator object when called.

|
|

Usually returns one result using return.

|

Can produce multiple results using yield.

|
|

Does not pause and resume at arbitrary yield points.

|

Can pause and resume at yield.

|
|

A normal function call returns its result directly.

|

next() is used to advance the generator.

|
|

Commonly used for calculations and operations.

|

Useful for sequences, custom iteration, and controlled execution.

|

Normal function

JavaScript

function getNumber() {
    return 10;
}

console.log(getNumber());

Output:

10

Generator function

JavaScript

function* getNumbers() {
    yield 10;
    yield 20;
}

const generator = getNumbers();

console.log(generator.next().value);
console.log(generator.next().value);

Output:

10
20

The normal function returns one result, whereas the generator can produce multiple results over time.

18. Difference Between Iterators and Generators

|
Iterator

|

Generator

|
| --- | --- |
|

An object that follows the iterator protocol.

|

A special function that produces a generator object.

|
|

Must provide a next() method.

|

Automatically provides iterator behavior.

|
|

Often requires manual state management.

|

Maintains execution state automatically.

|
|

Can be created using an object.

|

Created using function*.

|
|

Can be used with for...of if it is iterable or supplied appropriately.

|

Generator objects are iterable.

|
|

Useful for custom iteration.

|

Useful for creating custom iterators more easily.

|

Iterator example

JavaScript

const iterator = {
    current: 1,

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

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

console.log(iterator.next());

Generator example

JavaScript

function* numbers() {
    yield 1;
    yield 2;
    yield 3;
}

console.log(numbers().next());

The generator provides a simpler way to create the same kind of sequential value production.

19. Important Characteristics of Generators

19.1 Lazy execution

Generators produce values only when requested.

JavaScript

function* example() {
    console.log("Executed");
    yield 10;
}

const generator = example();

console.log("Generator created");