How to Define a Type for forEach in TypeScript: Generic Implementation Patterns

Define a type for forEach in TypeScript by declaring generic parameters T for the element type and U for the callback return type, allowing the compiler to infer types from the collection being iterated.

The microsoft/TypeScript repository demonstrates canonical patterns for typing forEach-style utilities using generic constraints. Whether you are building a compiler utility or a domain-specific collection library, understanding how to define a type for forEach in TypeScript ensures type safety across heterogeneous collections.

The Core Generic Pattern in TypeScript

The TypeScript compiler implements a foundational forEach helper in src/compiler/core.ts that serves as the reference implementation. This utility is generic over two type parameters: T represents the element type of the array, while U represents the return type of the callback function.

The signature at line 33 handles optional arrays and early termination:

export function forEach<T, U>(
    array: readonly T[] | undefined,
    callback: (element: T, index: number) => U | undefined
): U | undefined;

This pattern allows the compiler to infer T from the array argument and U from the callback's return statement, maintaining strict type checking without explicit annotations at the call site.

Implementing a Custom forEach Type

Basic Array Implementation

To define a type for a custom forEach that processes every element without returning a value, use a single generic parameter for the element type:

function forEach<T>(
  items: readonly T[],
  callback: (item: T, index: number) => void
): void {
  for (let i = 0; i < items.length; i++) {
    callback(items[i], i);
  }
}

Handling Optional Collections

Following the pattern in src/compiler/core.ts, modify the signature to accept undefined for defensive programming:

function forEach<T, U>(
  items: readonly T[] | undefined,
  callback: (item: T, index: number) => U | undefined
): U | undefined {
  if (!items) return undefined;
  
  for (let i = 0; i < items.length; i++) {
    const result = callback(items[i], i);
    if (result !== undefined) return result;
  }
  return undefined;
}

This implementation returns the first non-undefined value from the callback, enabling early-exit semantics similar to the TypeScript compiler's internal utilities.

Advanced forEach Type Patterns

Overloads for Complex Collections

The TypeScript repository includes React typings that demonstrate function overloads for forEach when handling polymorphic collections. In tests/lib/react18/react18.d.ts, the Children.forEach method uses overloads to handle both single children and arrays:

// react18.d.ts lines 386 and 3035
forEach<C>(children: C | ReadonlyArray<C>, 
           fn: (child: C, index: number) => void): void;

This pattern preserves the specific child type C whether the input is a single element or a collection, preventing unnecessary widening to unknown or any.

Record and Map Iteration

For dictionary-like structures, define generics for both keys and values:

function forEach<K extends string, V, U>(
  record: Record<K, V> | undefined,
  callback: (value: V, key: K) => U | undefined
): U | undefined {
  if (!record) return undefined;
  
  for (const key in record) {
    if (Object.prototype.hasOwnProperty.call(record, key)) {
      const result = callback(record[key], key as K);
      if (result !== undefined) return result;
    }
  }
  return undefined;
}

Using the Built-in Array.forEach Type

TypeScript's standard library defines the native Array.prototype.forEach in lib.es5.d.ts with a simpler signature optimized for side effects:

interface Array<T> {
  forEach(
    callbackfn: (value: T, index: number, array: T[]) => void,
    thisArg?: any
  ): void;
}

This built-in type automatically infers T from the array instance and enforces that the callback returns void, distinguishing it from transformation methods like map or filter.

Summary

  • Use two generics (T for elements, U for return values) when defining a type for forEach that may return early, as demonstrated in src/compiler/core.ts.
  • Accept readonly T[] | undefined to make utilities defensive against nullish inputs without requiring call-site guards.
  • Leverage function overloads for polymorphic collections where the same function must handle both single items and arrays, preserving specific type information.
  • Distinguish between side-effect and transformation semantics: native Array.forEach returns void, while custom utilities may return U | undefined to enable early termination.

Frequently Asked Questions

What is the difference between typing forEach and map in TypeScript?

forEach is designed for side effects and typically returns void or U | undefined for early exit, while map always returns a new array of type U[] with the same length as the input. When defining types, map requires a return type parameter that becomes the array element type, whereas forEach either ignores the callback return or uses it for short-circuit logic.

How do I type a forEach callback that modifies external state?

When a forEach callback modifies variables outside its scope, declare the state type explicitly and use a void return type for the callback to indicate side effects only. For example: forEach(items: T[], callback: (item: T) => void). If the external state is complex, consider passing it as a parameter with a specific interface rather than relying on closure capture, which improves type safety and testability.

Can I use forEach with async callbacks in TypeScript?

The standard Array.prototype.forEach does not handle async callbacks properly because it does not await promises and does not return a promise itself. To type an async-capable forEach, define the callback as returning Promise<void> or Promise<U> and wrap the iteration in an async function that awaits each callback. However, for async operations, Promise.all with map is generally preferred over forEach to ensure proper error handling and sequencing.

Why does the TypeScript compiler use its own forEach helper instead of the native array method?

The TypeScript compiler defines its own forEach in src/compiler/core.ts to support early termination (returning the first non-undefined value) and to safely handle undefined inputs without runtime errors. The native Array.prototype.forEach always iterates the entire array and returns void, which is inefficient for compiler operations that frequently search for specific nodes or symbols and need to exit early upon finding a match.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →