Official Doc

Yes, Conditional Types on Generics are Distributive.

When conditional types act on a generic type, they become distributive when given a union type.

Below is the official example.

type ToArray<Type> = Type extends any ? Type[] : never;

extends is like a if clause, if Type is string, it would be:

type A = string extends any ? string[] : never;

The any here is nothing special, we can use unknown or {} or Object as well to make it truthy.

type StrArrOrNumArr = ToArray<string | number>;
// string[] | number[]

At first sight, above result might better be Array<string | number>, but becaue Conditional Type is Distributive, it works more like below

ToArray<A | B | C> = ToArray<A> | ToArray<B> | ToArray<C>;

Above is what it means by Distributivity.

More examples

I’ve put a playground to show how it works.

type StringNumber = string | number;
type B2 = StringNumber extends any ? StringNumber[] : never;
//   StringNumber[]

Distributivity only works on generics. Above conditional type is not distributive.

type ToStringArray<Type> = Type extends string ? Type[] : never;
type B3 = ToStringArray<StringNumber>;
// string[]

Above generic is distributive, so B3 is ToStringArray<string> | ToStringArray<number>, ToStringArray<string> | never so string[].

type MyExclude<T, E> = T extends E ? never : T;
type C1 = MyExclude<string | number, number>;
//   string

Now we can understand why Exclude<T> could be written with extends, because it is distributive.

type MyExcludeReversed<T, E> = E extends T ? never : E;
type C2 = MyExcludeReversed<number, string | number>;
//   string

type MyExclude2<T, E> = T extends E ? [T, E] : never;
type C3 = MyExclude2<number | string | boolean, number | string | bigint>;
// [string, string | number | bigint] | [number, string | number | bigint]

We can see only T is distributive, not E, it depends on the position in extends not in the generic.

What if we want to it to be distributive on E as well?, Simple, put E before extends.

type MyExclude3<T, E> = T extends E ? (E extends any ? [T, E] : never) : never;
type C3 = MyExclude3<number | string | boolean, number | string | bigint>;

// [string, string] |
//  [never, number] |
//  [never, bigint] |
//  [never, string] |
//  [number, number]

Huh? It doesn’s seem to be result I expect, let’s change it a little bit

type MyExclude4<T, E> = T extends any
  ? E extends any
    ? [T, E]
    : [never]
  : never;
type C3 = MyExclude4<number | string | boolean, number | string | bigint>;
// [string, string]  |
//  [string, number] |
//  [string, bigint] |
//  [number, string] |
//  [number, number] |
//  [number, bigint]

This is more we wanted. Ok, let’s break down the previous snippet.

type C3 = MyExclude3<number | string | boolean, number | string | bigint>;

So for the first extends, T is distributed, boolean is filtered out, so it is identical to

MyExclude3<number, number | string | bigint> |
  MyExclude3<string, number | string | bigint>;

Then E is distributed, notice that T extends E still needs to be satisfied, so

  [number, number] |
  [never, string]  |
  [never, bigint]  |
  [never, number]  |
  [string, string] |
  [never, bigint]  |

Remove the duplicate one and we get the previous result.

Awesome, now we truely understand the distributivity.