Conditional types select one of two types based on a condition.
Basic Syntax
T extends U ? X : Y
If T extends U, the type is X, otherwise Y.
Simple Conditional Types
type IsArray<T> = T extends Array<any> ? true : false;
type Test1 = IsArray<string[]>; // true
type Test2 = IsArray<string>; // false
Type Inference in Conditional Types
Use infer to extract types.
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
Multiple infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Func = () => string;
type Ret = ReturnType<Func>; // string
Distributive Conditional Types
Conditional types distribute over union types.
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>;
// string[] | number[]
// To prevent distribution, use brackets
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type StrOrNumArr = ToArrayNonDist<string | number>;
// (string | number)[]
Non-Nullable Type
type NonNullable<T> = T extends null | undefined ? never : T;
type T0 = NonNullable<string | number | undefined>;
// string | number
Extract and Exclude
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type T0 = Exclude<"a" | "b" | "c", "a">;
// "b" | "c"
type T1 = Extract<"a" | "b" | "c", "a" | "f">;
// "a"
Function Parameter Types
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type Func = (a: string, b: number) => void;
type Params = Parameters<Func>;
// [string, number]
Return Type
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
type Func = () => string;
type Ret = ReturnType<Func>;
// string
Exercises
Exercise 1: Basic Conditional
// Create a conditional type 'IsString' that:
// - Returns true if T is string
// - Returns false otherwise
Exercise 2: Type Inference
// Create a conditional type 'ElementType' that:
// - Extracts the element type from an array
// - Returns T if T is not an array
Exercise 3: Distributive Types
// Create a conditional type that:
// - Distributes over a union
// - Transforms each member
// - Test with string | number
Exercise 4: Function Types
// Create a conditional type 'FirstParameter' that:
// - Extracts the first parameter type from a function
// - Returns never if not a function
Exercise 5: Complex Conditional
// Create a conditional type that:
// - Checks if T is a Promise
// - If yes, extracts the resolved type
// - If no, returns T
Best Practices
- Understand distribution - Conditional types distribute over unions
- Use infer for extraction - Extract types from complex types
- Prevent distribution when needed - Use brackets
- Combine with mapped types - Powerful type transformations
- Test your types - Ensure they work as expected
Common Mistakes
- Not understanding distribution
// This distributes
type T = ToArray<string | number>; // string[] | number[]
// This doesn't
type T = ToArrayNonDist<string | number>; // (string | number)[]
- Wrong infer placement
// ❌ Wrong
type Bad<T> = T extends Array<any> ? infer U : T;
// ✅ Correct
type Good<T> = T extends Array<infer U> ? U : T;
- Forgetting extends constraint
// ❌ Might not work as expected
type Bad<T> = T extends any ? T : never;
// ✅ Better with constraint
type Good<T extends any> = T extends any ? T : never;
Next Steps
Learn about Error Handling.