🔷 TypeScript Q60 / 101

Explain distributed conditional types.

AI-Powered Answer ✓ Answered

Distributed conditional types in TypeScript are a powerful mechanism that allows conditional types to 'distribute' over the members of a union type when the type parameter being checked is a naked type parameter in the extends clause. This enables sophisticated type transformations and filtering operations on union types.

What are Conditional Types?

Before diving into distribution, let's briefly recap conditional types. A conditional type selects one of two possible types based on a condition that checks the relationship between two types. Their syntax is T extends U ? X : Y, meaning 'if type T is assignable to type U, then the type is X; otherwise, it's Y'.

The Concept of Distribution

Distribution occurs when the type parameter on the left side of the extends keyword in a conditional type is a 'naked' type parameter, and that type parameter is instantiated with a union type. When this happens, TypeScript applies the conditional type to each member of the union individually, and then reunites the results into a new union type.

Essentially, if you have a conditional type type MyType<T> = T extends U ? A : B; and you call MyType<X | Y | Z>, TypeScript doesn't evaluate (X | Y | Z) extends U ? A : B. Instead, it evaluates (X extends U ? A : B) | (Y extends U ? A : B) | (Z extends U ? A : B).

Syntax and Mechanism

typescript
type Distributed<T> = T extends U ? X<T> : Y<T>;

If T is a union type like A | B | C, the type Distributed<T> effectively becomes:

typescript
(A extends U ? X<A> : Y<A>) | 
(B extends U ? X<B> : Y<B>) | 
(C extends U ? X<C> : Y<C>)

Practical Examples

Distributed conditional types are crucial for creating utility types that filter, transform, or extract specific parts of a union type.

Example 1: Filtering Non-Nullable Types

A common use case is removing null and undefined from a type. TypeScript's built-in NonNullable<T> utility type uses this principle.

typescript
type NonNullableCustom<T> = T extends null | undefined ? never : T;

type MyUnion = string | number | null | undefined;

type Result = NonNullableCustom<MyUnion>;
// Result is inferred as: string | number

Explanation of NonNullableCustom<MyUnion>:

  • string extends null | undefined ? never : string evaluates to string.
  • number extends null | undefined ? never : number evaluates to number.
  • null extends null | undefined ? never : null evaluates to never.
  • undefined extends null | undefined ? never : undefined evaluates to never.

The results are then reunited: string | number | never | never, which simplifies to string | number (as never is the bottom type and disappears in a union).

Example 2: Extracting Specific Members

You can use this to extract only specific types from a union, like string members.

typescript
type ExtractStrings<T> = T extends string ? T : never;

type MixedUnion = string | number | boolean;

type OnlyStrings = ExtractStrings<MixedUnion>;
// OnlyStrings is inferred as: string

Explanation of ExtractStrings<MixedUnion>:

  • string extends string ? string : never evaluates to string.
  • number extends string ? number : never evaluates to never.
  • boolean extends string ? boolean : never evaluates to never.

The reunited union is string | never | never, which simplifies to string.

When Does Distribution Occur?

Distribution only occurs when the type parameter being checked (the 'left side' of extends) is a 'naked type parameter'. This means it's not wrapped in another type constructor. For example, T in T extends U is a naked type parameter, but [T] in [T] extends [U] or Promise<T> in Promise<T> extends Promise<U> are not.

typescript
type NotDistributed<T> = [T] extends [string] ? T : never;

type NakedUnion = string | number;

type ResultNotDistributed = NotDistributed<NakedUnion>;
// ResultNotDistributed is inferred as: never

// Explanation:
// [string | number] extends [string] ? (string | number) : never;
// The condition '[string | number] extends [string]' is false, because 'number' is not assignable to 'string'.
// Therefore, the result is 'never'. If it distributed, it would be 'string'.

Conclusion

Distributed conditional types are a fundamental concept in advanced TypeScript, enabling the creation of highly flexible and powerful utility types. They are essential for manipulating and refining union types, allowing developers to precisely control type inference and achieve complex type-level logic.