Conditional generics
A construct of the form T extends U ? X : Y in TypeScript is called a
conditional type.
It works similarly to the ?: operator in JavaScript,
but it is applied at the type level, not to values.
General form
T extends U ? X : YThis can be read as:
"If type
Tis assignable to typeU, the result is typeX, otherwise, typeY."
Example 1: Basic example
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"If
Tis assignable tostring,"yes"is returned, otherwise"no".
Example 2: Checking a subtype
type ExtendsExample<T> = T extends number ? number : string;
let a: ExtendsExample<42>; // number
let b: ExtendsExample<"foo">; // stringHere TypeScript checks
whether T is a subtype of number; if so, it returns number.
Example 3: A complex type with objects
type HasId<T> = T extends { id: any } ? "Has ID" : "No ID";
type A = HasId<{ id: number; name: string }>; // "Has ID"
type B = HasId<{ name: string }>; // "No ID"Works even with object structures (structural type checking).
Example 4: Extracting a type with infer
Conditional types are often combined with infer to extract nested types:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number"If
TisPromise<X>, returnX, otherwise returnTitself."
Example 5: Filtering types
type ExcludeString<T> = T extends string ? never : T;
type Result = ExcludeString<string | number | boolean>;
// number | booleanHere TypeScript applies the condition to each member of the union type (string, number, boolean),
then combines the results, which is called distributive behavior of conditional types.
Example 6: The reverse case: extracting only strings
type ExtractString<T> = T extends string ? T : never;
type A = ExtractString<string | number | boolean>;
// stringExample 7: Usage with keyof
type PropertyExists<T, K> = K extends keyof T ? true : false;
type Obj = { id: number; name: string };
type A = PropertyExists<Obj, "id">; // true
type B = PropertyExists<Obj, "age">; // falseWe check whether a specific key
Kexists on objectT.
Example 8: Comparison with "if" at the value level
JS:
const result = condition ? value1 : value2;TS type:
type Result<T> = T extends true ? "yes" : "no";Works at compile time, not while the code runs.
How this works internally
-
T extends Uis a type compatibility check (not inheritance). -
If
Tis a union (for exampleA | B | C), the conditional type is applied to each member of the union separately:javascript(A | B) extends U ? X : Y -> (A extends U ? X : Y) | (B extends U ? X : Y)
This is called the distributive behavior of conditional types.
Example 9: The non-distributive variant
To turn off "distribution" over a union type,
T is wrapped in a tuple [...]:
type NonDistributive<T> = [T] extends [string] ? "yes" : "no";
type A = NonDistributive<string | number>;
// "no"Without the brackets, the result would be
"yes" | "no".
Example 10: Implementing built-in utilities with conditional types
| Utility | Implementation | Description |
|---|---|---|
Exclude<T, U> | T extends U ? never : T | Removes from T every type assignable to U |
Extract<T, U> | T extends U ? T : never | Keeps only the types from T that are assignable to U |
NonNullable<T> | `T extends null | undefined ? never : T` |
ReturnType<T> | T extends (...args: any[]) => infer R ? R : any | Extracts the return type of a function |
Summary
| Element | Meaning |
|---|---|
| Syntax | T extends U ? X : Y |
| Meaning | "If T is a subtype of U, then X, otherwise Y" |
| Applies to | types, not values |
| Feature | works "distributively" with union types |
| Often used with | infer, Extract, Exclude, NonNullable, ReturnType |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.