ZetCode

TypeScript Iterators

last modified October 15, 2026

An iterator is an object that produces a sequence of values, one at a time. JavaScript defines two protocols for this. An iterator has a next method that returns objects of the form { value, done }. An iterable is an object with a [Symbol.iterator] method that returns an iterator. Arrays, strings, maps, and sets are iterable, and so is any object that implements the protocol. TypeScript describes the protocols with the Iterator<T> and Iterable<T> types.

Basic Iterator Protocol

Calling the [Symbol.iterator] method of an array returns an iterator. Each call to next returns the next value.

basic_iterator.ts
const numbers = [1, 2, 3];
const iterator = numbers[Symbol.iterator]();

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

The first three calls return the elements with done: false. The fourth call signals the end of the sequence with done: true and an undefined value. The for...of loop does exactly this behind the scenes: it calls next until done is true.

$ npx tsx basic_iterator.ts
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

Consuming Iterables

Besides for...of, several other language features accept any iterable.

consuming_iterables.ts
const word = "hey";
const unique = new Set([3, 1, 3, 2, 1]);

const letters = [...word];
const sorted = Array.from(unique).sort();
const [first, ...rest] = unique;

console.log(letters);
console.log(sorted);
console.log(first, rest);
console.log(Math.max(...unique));

The spread operator, Array.from, and array destructuring all iterate over their argument. They work the same way for a string, a set, or a custom iterable. TypeScript infers the element types: letters is a string[] and first a number.

$ npx tsx consuming_iterables.ts
[ 'h', 'e', 'y' ]
[ 1, 2, 3 ]
3 [ 1, 2 ]
3

Custom Iterators

A class becomes iterable by implementing the [Symbol.iterator] method. The implements Iterable<number> clause lets the compiler check the method.

custom_iterator.ts
class NumberRange implements Iterable<number> {
    constructor(private start: number, private end: number, private step = 1) {}

    [Symbol.iterator](): Iterator<number> {
        let current = this.start;

        return {
            next: (): IteratorResult<number> => {
                if (current <= this.end) {
                    const value = current;
                    current += this.step;
                    return { value, done: false };
                }

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

const range = new NumberRange(1, 10, 3);

for (const num of range) {
    console.log(num);
}

console.log([...range]);

Each call to [Symbol.iterator] creates a new iterator object with its own current variable. That is why the range can be iterated twice: once with the loop and once with the spread operator. The IteratorResult<number> return type covers both kinds of results, the values with done: false and the final one with done: true.

$ npx tsx custom_iterator.ts
1
4
7
10
[ 1, 4, 7, 10 ]

Generators

Writing the iterator object by hand is verbose. A generator function, declared with function*, creates iterators automatically.

generator.ts
function* generateSequence(start: number, end: number): Generator<number> {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

for (const num of generateSequence(1, 3)) {
    console.log(num);
}

class Countdown implements Iterable<number> {
    constructor(private from: number) {}

    *[Symbol.iterator](): Generator<number> {
        for (let i = this.from; i > 0; i--) {
            yield i;
        }
    }
}

console.log([...new Countdown(5)]);

Each yield produces one value and pauses the function until the next value is requested. The Countdown class shows the most convenient way to make a class iterable: a generator method named *[Symbol.iterator]. See the generators tutorial for more details.

$ npx tsx generator.ts
1
2
3
[ 5, 4, 3, 2, 1 ]

One-Shot Iterators

An iterable creates a new iterator each time. An iterator itself, such as the object returned by a generator function, can be consumed only once.

one_shot.ts
function* letters(): Generator<string> {
    yield "a";
    yield "b";
    yield "c";
}

const gen = letters();

console.log([...gen]);
console.log([...gen]);

const array = ["a", "b", "c"];

console.log([...array]);
console.log([...array]);

The second spread of gen produces an empty array, because the generator has already finished. The array can be spread any number of times, because every spread asks it for a fresh iterator. To iterate generated values several times, call the generator function again or store the values in an array.

$ npx tsx one_shot.ts
[ 'a', 'b', 'c' ]
[]
[ 'a', 'b', 'c' ]
[ 'a', 'b', 'c' ]

Iterating Over Maps

Maps are iterable and provide three iterators: over keys, over values, and over entries.

map_iterator.ts
const stock = new Map<string, number>([
    ["apples", 5],
    ["pears", 0],
    ["plums", 12],
]);

for (const [fruit, count] of stock) {
    console.log(`${fruit}: ${count}`);
}

console.log([...stock.keys()]);
console.log([...stock.values()].reduce((a, b) => a + b, 0));

const available = [...stock].filter(([, count]) => count > 0).map(([fruit]) => fruit);
console.log(available);

Iterating the map directly is the same as iterating stock.entries(): each element is a [key, value] pair, which the loop destructures. The keys and values methods return separate iterators. Maps keep the insertion order. Spreading a map into an array of pairs makes array methods such as filter and map available.

$ npx tsx map_iterator.ts
apples: 5
pears: 0
plums: 12
[ 'apples', 'pears', 'plums' ]
17
[ 'apples', 'plums' ]

Iterating Over Sets

Sets are iterable as well. They store each value only once.

set_iterator.ts
const visits = ["home", "about", "home", "blog", "about", "home"];
const pages = new Set(visits);

for (const page of pages) {
    console.log(page);
}

console.log(pages.size, [...pages].join(", "));

Creating a set from an array removes duplicates while keeping the order of the first occurrences. The size property returns the number of unique values. The type of pages is Set<string>, inferred from the array.

$ npx tsx set_iterator.ts
home
about
blog
3 home, about, blog

Lazy and Infinite Sequences

Iterators compute values only when they are requested. This makes it possible to describe infinite sequences and take only as many values as needed.

lazy_sequence.ts
function* naturals(): Generator<number> {
    let n = 1;
    while (true) {
        yield n++;
    }
}

function* take<T>(source: Iterable<T>, count: number): Generator<T> {
    if (count <= 0) {
        return;
    }

    let taken = 0;
    for (const item of source) {
        yield item;
        if (++taken >= count) {
            return;
        }
    }
}

function* filter<T>(source: Iterable<T>, predicate: (item: T) => boolean): Generator<T> {
    for (const item of source) {
        if (predicate(item)) {
            yield item;
        }
    }
}

const squares = filter(naturals(), n => Number.isInteger(Math.sqrt(n)));
console.log([...take(squares, 5)]);

The naturals generator never ends, yet the program finishes, because take stops after five values. The filter and take helpers work with any iterable, and TypeScript infers their type parameter T as number here. Each value passes through the whole chain before the next one is generated.

$ npx tsx lazy_sequence.ts
[ 1, 4, 9, 16, 25 ]

Async Iterators

Async iterators produce values that become available over time, such as pages of results from an API. They are created with async generators and consumed with for await...of.

async_iterator.ts
type Page = { page: number; items: string[] };

function delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function fetchPage(page: number): Promise<Page> {
    await delay(50);
    return { page, items: [`item ${page * 2 - 1}`, `item ${page * 2}`] };
}

async function* fetchAll(pages: number): AsyncGenerator<string> {
    for (let page = 1; page <= pages; page++) {
        const result = await fetchPage(page);
        yield* result.items;
    }
}

async function main(): Promise<void> {
    for await (const item of fetchAll(3)) {
        console.log(item);
    }
}

main();

The fetchPage function simulates a slow API call. The fetchAll async generator requests one page at a time and yields its items individually; yield* delegates to the items array. The caller sees a simple stream of strings and does not need to know about pages. The return type AsyncGenerator<string> makes item a string.

$ npx tsx async_iterator.ts
item 1
item 2
item 3
item 4
item 5
item 6

Combining Iterators

Generators can delegate to other iterables with yield*. This makes it easy to combine sources into one sequence.

combine_iterators.ts
function* concat<T>(...sources: Iterable<T>[]): Generator<T> {
    for (const source of sources) {
        yield* source;
    }
}

function* zip<A, B>(a: Iterable<A>, b: Iterable<B>): Generator<[A, B]> {
    const itA = a[Symbol.iterator]();
    const itB = b[Symbol.iterator]();

    while (true) {
        const x = itA.next();
        const y = itB.next();

        if (x.done || y.done) {
            return;
        }

        yield [x.value, y.value];
    }
}

console.log([...concat([1, 2], new Set([3, 4]), [5])]);

for (const [name, score] of zip(["Alice", "Bob", "Carol"], [92, 78])) {
    console.log(`${name}: ${score}`);
}

The concat function chains any number of iterables of the same type; here an array, a set, and another array. The zip function walks two iterables in parallel by calling next on both iterators and stops when the shorter one ends, so Carol, who has no score, is not printed. The yielded pairs have the tuple type [string, number].

$ npx tsx combine_iterators.ts
[ 1, 2, 3, 4, 5 ]
Alice: 92
Bob: 78

Best Practices

Source

TypeScript Handbook: Iterators and Generators

This tutorial covered iterators in TypeScript. Any object that implements Symbol.iterator works with for...of, spreading, and destructuring, and generators are the simplest way to write one.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.