Conditional types in TypeScript let you choose a type based on a condition — they follow the same logic as a ternary expression.
The basic syntax is:
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
The infer keyword is where it gets powerful — it lets you extract a type from within the condition:
// Extract the return type of any function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = () => { id: number; name: string };
type Result = ReturnType<Fn>; // { id: number; name: string }
Think of `infer R` as: "if this matches, capture whatever is in that position as R."
Want me to show more practical examples — like extracting Promise types or array element types?
Great use cases! Here are both patterns:
Unwrap array element type:
type ElementOf<T> = T extends Array<infer E> ? E : never;
type Names = string[];
type Name = ElementOf<Names>; // string
type Nested = [number, string, boolean];
type First = ElementOf<Nested>; // number | string | boolean
Unwrap Promise value:
type Awaited<T> = T extends Promise<infer V> ? V : T;
type ApiResult = Promise<{ id: number; email: string }>;
type Data = Awaited<ApiResult>; // { id: number; email: string }
Both combined — async function return value:
type AsyncReturnType<T extends (...args: any[]) => Promise<any>> =
T extends (...args: any[]) => Promise<infer R> ? R : never;
async function fetchUser() {
return { id: 1, name: 'Sofia', role: 'admin' };
}
type User = AsyncReturnType<typeof fetchUser>;
// { id: number; name: string; role: string }
Note: TypeScript 4.5+ ships with a built-in `Awaited
Distributive conditional types are one of TypeScript's more surprising behaviors — and very useful once you understand them.
When you write a conditional type where the checked type is a bare type parameter, TypeScript automatically distributes it over union types:
type ToArray<T> = T extends any ? T[] : never;
// With a union:
type Result = ToArray<string | number>;
// Distributes to: ToArray<string> | ToArray<number>
// Final result: string[] | number[]
To prevent distribution, wrap the type parameter in a tuple:
type NoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = NoDistribute<string | number>;
// Does NOT distribute — checks the union as a whole
// Result: (string | number)[]
Practical use — filtering union types:
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type CleanString = NonNullable<MaybeString>; // string
This is exactly how TypeScript's built-in `NonNullable
ChatMind can make mistakes. Verify important information independently.