Distributive Conditional Types in TypeScript

When a conditional type receives a union, TypeScript distributes the check over each member. Learn how distribution works, when to use it, and how to disable it.

7 min read

TypeScript distributive conditional types are what happens when a conditional type receives a union. Instead of checking the union as a single type, TypeScript applies the condition to each member of the union separately, then combines the results back into a union.

Here is the simplest demonstration:

typescripttypescript
type ToArray<T> = T extends any ? T[] : never;
 
type Result = ToArray<string | number>;
//   ^? string[] | number[]

TypeScript evaluates the condition for each union member separately and combines the results into string[] | number[]. This distributive behavior is applied automatically whenever the checked type in a conditional type is a union, without any extra syntax.

This Mermaid diagram shows what happens step by step:

Distributive conditional type flow

TypeScript splits the union, evaluates the conditional for each member, and unions the outcomes. This is the default and usually desired behavior for generic conditional types.

When distribution happens

Distribution happens only when the checked type is a bare generic type parameter. A bare type parameter is just T, not T wrapped in something:

typescripttypescript
type Dist<T> = T extends any ? T[] : never;      // distributive
 
type NoDist<T> = [T] extends [any] ? T[] : never; // not distributive

In Dist the type parameter is bare, so it distributes. In NoDist it is wrapped in a tuple, so TypeScript treats the whole union as one value instead of splitting it apart. Passing the same union to both shows the difference clearly:

typescripttypescript
type A = Dist<string | number>;
//   ^? string[] | number[]
 
type B = NoDist<string | number>;
//   ^? (string | number)[]

NoDist checks the entire union against any in one shot and produces a single array type whose element type is itself a union, rather than a union of two separate array types.

Other wrappers that disable distribution include T & {}, T | never, and { prop: T }. Any transformation that makes the parameter not bare disables distribution.

Built-in types that use distribution

Several built-in utility types rely on distributive behavior:

typescripttypescript
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T extends null | undefined ? never : T;

Exclude removes types from a union. When the first type parameter is string | number | boolean and the second is string or boolean, TypeScript distributes over the first union and checks each member against the second:

typescripttypescript
type Result = MyExclude<string | number | boolean, string | boolean>;
//   ^? number

The step-by-step:

MemberCheckResult
stringextends string or boolean → truenever
numberextends string or boolean → falsenumber
booleanextends string or boolean → truenever

Members that match vanish from the union, leaving only number. Extract works the same way but keeps the matching members instead of discarding them, which makes it the mirror image of Exclude:

typescripttypescript
type Result = MyExtract<string | number | boolean, string | boolean>;
//   ^? string | boolean

NonNullable applies the identical distributive pattern to strip null and undefined values out of a union, which is useful right after narrowing an optional value:

typescripttypescript
type Clean = MyNonNullable<string | null | undefined>;
//   ^? string

All three utility types exist because distribution lets a conditional type process every union member on its own, then reassemble the results.

The never behavior

The type never is the empty union. When a distributive conditional type receives never, there are no members to distribute over, so the result is never as well:

typescripttypescript
type Check<T> = T extends string ? "yes" : "no";
 
type Result = Check<never>;
//   ^? never

This can be surprising if you expect the false branch to run. Since there are no members, the conditional does not run at all, and TypeScript falls back to never.

To avoid this, disable distribution by wrapping the type parameter in square brackets:

typescripttypescript
type CheckSafe<T> = [T] extends [string] ? "yes" : "no";
 
type Result = CheckSafe<never>;
//   ^? "yes"

With [T], never is checked as a single type instead of a set of members to distribute over. Here the check passes because never is assignable to every type, so the true branch runs.

When to disable distribution

Distribution is usually what you want, but not always. Disable it when you need to check the union as a single type rather than its individual members.

A common case is when you want to check whether an entire type "contains" or "matches" a pattern, rather than filtering its members:

typescripttypescript
type IsArray<T> = T extends any[] ? true : false;
 
type A = IsArray<string[] | number>;
//   ^? boolean

The result is true | false (which collapses to boolean) because distribution runs the check on string[] and number separately, once per member. If you wanted to ask "is this union itself an array?", you would disable distribution:

typescripttypescript
type IsArrayUnion<T> = [T] extends [any[]] ? true : false;
 
type B = IsArrayUnion<string[] | number>;
//   ^? false

Now the whole union is checked as one type, and a union of an array and a non-array is not itself an array.

Distributive conditional types with infer

When infer is used inside a distributive conditional type that receives a union, distribution applies first, and then infer captures a piece from each member on its own:

typescripttypescript
type ElementType<T> = T extends Array<infer Item> ? Item : never;
 
type Result = ElementType<string[] | number[]>;
//   ^? string | number

TypeScript distributes over string[] and number[] separately, capturing string from one and number from the other, then unions the captured types. For more on the capture mechanism itself, see Infer Keyword in TypeScript.

Filtering object types with distribution

You can combine distribution with conditional checks on object shapes to filter a union of object types:

typescripttypescript
type HasId = { id: number };
 
type WithId<T> = T extends HasId ? T : never;
 
type A = { id: number; name: string };
type B = { title: string };
type C = { id: number; age: number };
 
type Filtered = WithId<A | B | C>;
//   ^? A | C

The B type does not have an id property, so the check fails and it maps to never, vanishing from the result.

Common mistakes

Expecting distribution with non-bare type parameters. { p: T } extends { p: U } does not distribute. Only a bare type parameter triggers distribution.

Assuming never is handled like other types. A distributive conditional type returns never when it receives never, because there are no members to process. Wrap in [T] if you need to detect it directly.

Using distribution for runtime behavior. Distributive conditional types are purely a compile-time mechanism. They do not affect runtime values or control flow.

Writing overly complex distributive types. When a single distributive conditional type grows beyond a few lines, consider breaking it into smaller named type aliases. This makes the logic easier to test and understand.

Rune AI

Rune AI

Key Insights

  • Distribution splits a union and applies the conditional to each member separately.
  • Bare generic type parameters trigger distribution; wrapped ones do not.
  • Exclude, Extract, and NonNullable all rely on distribution.
  • Wrap in [T] to disable distribution and check a union as a whole.
  • never is the empty union; distributing over it always produces never.
RunePowered by Rune AI

Frequently Asked Questions

What makes a conditional type distributive?

A conditional type is distributive when the checked type is a bare generic type parameter. If that type is a union, TypeScript applies the conditional to each union member separately and unions the results.

How do I disable distributive behavior?

Wrap both sides of `extends` in square brackets: `[T] extends [U] ? X : Y`. This treats the union as a single type instead of distributing over it.

Conclusion

Distributive conditional types apply a check to every member of a union individually and union the results. This is the default for bare generic type parameters and it powers the built-in Exclude, Extract, and NonNullable types. When you need the opposite behavior, wrap the types in square brackets.