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 | boolean