How TypeScript solved its global Iterator name clash

[2025-06-18] dev, typescript
(Ad, please don’t block)

In 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.

The conflict  

Class Iterator changes what iterators are:

  • Previously, an iterator was simply an object with a method .next().
  • Now, each iterator must be an instance of 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:

  • On one hand, there is the type Iterator for the old iterator objects.
  • On the other hand, class Iterator needs a type for its instances. Normally, that type would be Iterator.

TypeScript solves this as follows:

  • There is a global class Iterator.
  • Its instances have the type IteratorObject.
  • The type 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.

Intermission: Why are iterator helper methods useful?  

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:

  • They provide operations for all objects that support iteration.
  • No intermediate Arrays are created and processing happens incrementally.

For more information, see section “The benefits of iterator helper methods” in “Exploring JavaScript”.

Iteration in the past  

Past iteration: JavaScript  

In the past, JavaScript had the internal object %IteratorPrototype% with one property:

  • %IteratorPrototype%[Symbol.iterator]()
    • Method that returns 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%

Past iteration: TypeScript  

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>;
}

The new iteration protocol  

New iteration: JavaScript  

In ECMAScript 2025, a new global class Iterator was introduced. And %IteratorPrototype% was renamed to %Iterator.prototype%:

  • It is the value of Iterator.prototype.
  • It is still the prototype of objects such as:
    • %ArrayIteratorPrototype%
    • %MapIteratorPrototype%
    • %SetIteratorPrototype%
  • Its properties include:
    • Iterator helper methods such as .map()
    • Method [Symbol.iterator]() that returns this
    • Etc.

TypeScript: preparing for the new methods  

TypeScript makes one fundamental change at the type level (even in the types for ECMAScript 2015):

  • Old: IterableIterator is the type of all built-in iterators.
  • New: 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.

TypeScript: adding the new methods  

In the declarations for class Iterator, TypeScript must achieve the following goal:

  • Class Iterator must be a globally visible value.
  • Its instances must have the type 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;
}

What does this mean for our code?  

  • The type for iterables continues to be Iterable.
  • The best type for iterators is now IteratorObject. If we use it, the iterator helper methods are available.
  • If we hand-write an iterator, it must have Iterator.prototype in its prototype chain (more information).

Further reading  

My book “Exploring TypeScript” is free to read online:

  • It teaches TypeScript to JavaScript programmers.
  • It also teaches advanced TypeScript programming (computing at the type level, etc.).