Skip to main content

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

javascript
T extends U ? X : Y

This can be read as:

"If type T is assignable to type U, the result is type X, otherwise, type Y."


Example 1: Basic example

javascript
type IsString<T> = T extends string ? "yes" : "no"; type A = IsString<string>; // "yes" type B = IsString<number>; // "no"

If T is assignable to string, "yes" is returned, otherwise "no".


Example 2: Checking a subtype

javascript
type ExtendsExample<T> = T extends number ? number : string; let a: ExtendsExample<42>; // number let b: ExtendsExample<"foo">; // string

Here TypeScript checks whether T is a subtype of number; if so, it returns number.


Example 3: A complex type with objects

javascript
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:

javascript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T; type A = UnwrapPromise<Promise<string>>; // string type B = UnwrapPromise<number>; // number

"If T is Promise<X>, return X, otherwise return T itself."


Example 5: Filtering types

javascript
type ExcludeString<T> = T extends string ? never : T; type Result = ExcludeString<string | number | boolean>; // number | boolean

Here 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

javascript
type ExtractString<T> = T extends string ? T : never; type A = ExtractString<string | number | boolean>; // string

Example 7: Usage with keyof

javascript
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">; // false

We check whether a specific key K exists on object T.


Example 8: Comparison with "if" at the value level

JS:

javascript
const result = condition ? value1 : value2;

TS type:

javascript
type Result<T> = T extends true ? "yes" : "no";

Works at compile time, not while the code runs.


How this works internally

  • T extends U is a type compatibility check (not inheritance).

  • If T is a union (for example A | 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 [...]:

javascript
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

UtilityImplementationDescription
Exclude<T, U>T extends U ? never : TRemoves from T every type assignable to U
Extract<T, U>T extends U ? T : neverKeeps only the types from T that are assignable to U
NonNullable<T>`T extends nullundefined ? never : T`
ReturnType<T>T extends (...args: any[]) => infer R ? R : anyExtracts the return type of a function

Summary

ElementMeaning
SyntaxT extends U ? X : Y
Meaning"If T is a subtype of U, then X, otherwise Y"
Applies totypes, not values
Featureworks "distributively" with union types
Often used withinfer, Extract, Exclude, NonNullable, ReturnType

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.