Iterator
name clashIn ECMAScript 2025, JavaScript gets a class Iterator
with iterator helper methods. This class conflicts with TypeScript’s existing types for iterators. In this blog post, we explore why that is and how TypeScript solves that conflict.
Class Iterator
changes what iterators are:
.next()
.Iterator
– i.e., Iterator.prototype
must be one of its prototypes. As a consequence, the iterator inherits the new iterator helper methods.Alas, for TypeScript, that causes a conflict at the type level:
Iterator
for the old iterator objects.Iterator
needs a type for its instances. Normally, that type would be Iterator
.TypeScript solves this as follows:
Iterator
.IteratorObject
.Iterator
remains unchanged for old code and continues to only contain the core functionality of the iteration protocol (without iterator helper methods etc.).Read on to find out how that is set up by the built-in type declarations.
Why do we even need iterator helper methods such as:
Iterator.prototype.filter()
Iterator.prototype.map()
Why not simply use Array methods? Because iterator helper methods have two benefits:
For more information, see section “The benefits of iterator helper methods” in “Exploring JavaScript”.
In the past, JavaScript had the internal object %IteratorPrototype%
with one property:
%IteratorPrototype%[Symbol.iterator]()
this
.It is the prototype of all built-in iterators. Its method makes those iterators iterable. It is the prototype of objects such as:
%ArrayIteratorPrototype%
%MapIteratorPrototype%
%SetIteratorPrototype%
The following declarations show how TypeScript represents the old core iteration protocol at the type level (source):
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}
type IteratorResult<T, TReturn = any> =
| IteratorYieldResult<T>
| IteratorReturnResult<TReturn>
;
interface Iterator<T, TReturn = any, TNext = any> {
next(...[value]: [] | [TNext]): IteratorResult<T, TReturn>;
// Omitted: two optional methods
}
interface Iterable<T, TReturn = any, TNext = any> {
[Symbol.iterator](): Iterator<T, TReturn, TNext>;
}
Additionally, all built-in iterators (such as the iterators returned by generators) implemented the following interface – which expresses that they inherit from %IteratorPrototype%
and are therefore iterable:
/**
* Describes a user-defined {@link Iterator} that is also iterable.
*/
interface IterableIterator<T, TReturn = any, TNext = any>
extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): IterableIterator<T, TReturn, TNext>;
}
In ECMAScript 2025, a new global class Iterator
was introduced. And %IteratorPrototype%
was renamed to %Iterator.prototype%
:
Iterator.prototype
.%ArrayIteratorPrototype%
%MapIteratorPrototype%
%SetIteratorPrototype%
.map()
[Symbol.iterator]()
that returns this
TypeScript makes one fundamental change at the type level (even in the types for ECMAScript 2015):
IterableIterator
is the type of all built-in iterators.IteratorObject
is the new type of all built-in iterators.The declarations look as follows (source):
/**
* Describes an {@link Iterator} produced by the runtime that inherits from
* the intrinsic `Iterator.prototype`.
*/
interface IteratorObject<T, TReturn = unknown, TNext = unknown>
extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): IteratorObject<T, TReturn, TNext>;
}
/**
* Defines the `TReturn` type used for built-in iterators produced by
* `Array`, `Map`, `Set`, and others. This is `undefined` when
* `strictBuiltInIteratorReturn` is `true`; otherwise, this is `any`.
*/
type BuiltinIteratorReturn = intrinsic;
interface ArrayIterator<T>
extends IteratorObject<T, BuiltinIteratorReturn, unknown> {
[Symbol.iterator](): ArrayIterator<T>;
}
interface MapIterator<T>
extends IteratorObject<T, BuiltinIteratorReturn, unknown> {
[Symbol.iterator](): MapIterator<T>;
}
In a way, IterableIterator
was renamed to IteratorObject
, but IterableIterator
is still there so that old code that uses this type doesn’t break.
Note that types for objects are flat and that extending produces a new flat type with additional methods. That is different from JavaScript and its prototype chains.
Type IteratorObject
provides the foundation that can be augmented with the iterator helper methods. The next subsection explains how.
In the declarations for class Iterator
, TypeScript must achieve the following goal:
Iterator
must be a globally visible value.IteratorObject
. If Iterator
were a normal class, its instances would have the type Iterator
.Step 1 (of 5) to achieve that – define an internal class Iterator
(source):
declare abstract class Iterator<
T, TResult = undefined, TNext = unknown
> {
abstract next(value?: TNext): IteratorResult<T, TResult>;
}
The instances of class Iterator
have the type Iterator
. That type is also internal – which is why it doesn’t clash with the type Iterator
of the iteration protocol.
Step 2 of 5: Add the methods of IteratorObject
(which is defined later) to the instance type Iterator
:
// Merge all members of `IteratorObject<T>` into `Iterator<T>`
interface Iterator<T, TResult, TNext> extends
globalThis.IteratorObject<T, TResult, TNext> {}
Step 3 of 5: Create an internal alias for class Iterator
that we can use later:
type IteratorObjectConstructor = typeof Iterator;
IteratorObjectConstructor
is the type of the class and has properties such as .prototype
.
Step 4 of 5: Define the global type IteratorObject
for all built-in iterators.
declare global {
interface IteratorObject<T, TReturn, TNext> {
[Symbol.iterator](): IteratorObject<T, TReturn, TNext>;
map<U>(
callbackfn: (value: T, index: number) => U
): IteratorObject<U, undefined, unknown>;
// ···
}
// ···
}
Step 5 of 5: The global variable Iterator
has the type IteratorConstructor
which extends the previously defined IteratorObjectConstructor
and declares Iterator.from()
:
declare global {
// ···
interface IteratorConstructor extends IteratorObjectConstructor {
from<T>(
value: Iterator<T, unknown, undefined> | Iterable<T, unknown, undefined>
): IteratorObject<T, undefined, unknown>;
}
var Iterator: IteratorConstructor;
}
Iterable
.IteratorObject
. If we use it, the iterator helper methods are available.Iterator.prototype
in its prototype chain (more information).My book “Exploring TypeScript” is free to read online: