Distribution kicks in whenever the checked type is a naked type parameter — including when you didn't want it to. A helper meant to turn a type into "an array of it" quietly becomes "an array of each member" when you pass a union, so ToArray<string | number> is string[] | number[], not (string | number)[]. Then a perfectly ordinary mixed array is rejected, with an error that talks about string[].
type ToArray<T> = T extends any ? T[] : never;
type Mixed = ToArray<string | number>; // expected (string | number)[]
const values: Mixed = ["a", 1];
// error TS2322: Type '(string | number)[]' is not assignable to type 'Mixed'.
// Type '(string | number)[]' is not assignable to type 'string[]'.A conditional type has the form T extends U ? X : Y: if T is assignable to U, the result is X; otherwise Y — the type-level equivalent of a ternary expression. The infer keyword, used inside the extends clause, captures part of the matched type as a new named type variable usable in the result, which is how TypeScript's own built-in utility types like ReturnType<T> extract a function's return type without ever running any code.
A basic conditional type
This is evaluated entirely at compile time, purely on types — no runtime check, no generated code for the condition itself.
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"infer: extracting a type from within the condition
Rather than just returning a fixed X or Y, infer lets the result reuse a piece captured FROM the matched type itself — this exact pattern is how TypeScript's own ReturnType<T> is implemented.
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { name: "Alice", age: 30 };
}
type UserResult = MyReturnType<typeof getUser>;
// { name: string; age: number } — extracted, without calling getUser at allDistributive conditional types over a union
When T in T extends U ? X : Y is a "naked" type parameter and the actual argument is a union, TypeScript applies the conditional to EACH member of the union separately and unions the results — a subtle but genuinely useful behavior, since it's how filtering a union by type works.
type ExcludeString<T> = T extends string ? never : T;
type Result = ExcludeString<string | number | boolean>;
// distributes over each member: never | number | boolean
// simplifies to: number | booleanDistribution only applies to a naked type parameter, which is also the switch to turn it off. Wrapping both sides of extends in a one-element tuple makes T no longer naked, so the union is checked as a whole:
type ToArray<T> = [T] extends [any] ? T[] : never;
type Mixed = ToArray<string | number>; // (string | number)[]
const values: Mixed = ["a", 1];
console.log(values); // [ 'a', 1 ]