TS Conditional Types
A conditional type picks between two types based on a check. T extends U ? X : Y reads like a ternary — but operates on types.
Basics
TS
type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<'hi'>; // 'yes' type B = IsString<42>; // 'no'
Distribution over unions
If T is a union, the condition runs on each member:
TS
type ToArray<T> = T extends any ? T[] : never; type Result = ToArray<string | number>; // = string[] | number[] (distributed)
To prevent distribution, wrap in a tuple:
TS
type ToArray2<T> = [T] extends [any] ? T[] : never; type R = ToArray2<string | number>; // (string | number)[]
infer — pattern matching
TS
type ElementType<T> = T extends Array<infer U> ? U : T; type N = ElementType<number[]>; // number type S = ElementType<string>; // string (not an array — falls through)
How utility types are built
TS
type NonNullable<T> = T extends null | undefined ? never : T; type T1 = NonNullable<string | null | undefined>; // string
Real-world: extract return type
TS
type ReturnType<F> = F extends (...args: any[]) => infer R ? R : never; type R1 = ReturnType<() => number>; // number type R2 = ReturnType<(x: string) => string>; // string
Promise unwrap
TS
type Unwrap<T> = T extends Promise<infer R> ? R : T; type U = Unwrap<Promise<User>>; // User
Tip: Conditional types are powerful but easy to over-use. If you find yourself nesting three deep, take a step back — a discriminated union or a simpler generic usually models the problem just as well, with way fewer footguns.
Example
Example
type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<'hello'>; // 'yes' type B = IsString<42>; // 'no' const a: A = 'yes'; const b: B = 'no'; console.log(a, b);Try it Yourself »
Exercise
Capture a type during conditional matching with this keyword.
T extends Array<
U> ? U : T
Five letters.
Discussion
Loading…