TypeScript Conditional Types
Create types based on conditions
❓ What are Conditional Types?
Conditional types select one of two possible types based on a condition. They work like ternary operators for types, using the syntax T extends U ? X : Y to create dynamic type logic.
type IsString<T> = T extends string ? "yes" : "no";
type Result1 = IsString<string>; // "yes"
type Result2 = IsString<number>; // "no"
Key Concepts
Type Branching
Choose types based on conditions
type Check<T> = T extends string
? "text" : "other";
Type Inference
Extract types with infer keyword
type ReturnType<T> = T extends
(...args: any[]) => infer R ? R : never;
Distributive
Apply to union type members
type ToArray<T> = T extends any
? T[] : never;
Nested Conditions
Chain multiple conditions
type TypeName<T> = T extends string
? "string" : T extends number
? "number" : "other";
🔹 Basic Conditional Type
Create types that branch based on conditions:
type IsNumber<T> = T extends number ? true : false;
type Test1 = IsNumber<42>; // true
type Test2 = IsNumber<"hello">; // false
type Test3 = IsNumber<boolean>; // false
// Practical example
type MessageType<T> = T extends string ? "text" : "data";
type Msg1 = MessageType<string>; // "text"
type Msg2 = MessageType<number>; // "data"
🔹 Extracting Return Types
Use infer to extract types from functions:
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getString(): string {
return "hello";
}
function getNumber(): number {
return 42;
}
type StringReturn = GetReturnType<typeof getString>; // string
type NumberReturn = GetReturnType<typeof getNumber>; // number
// Using built-in ReturnType utility
type Return1 = ReturnType<typeof getString>; // string
type Return2 = ReturnType<typeof getNumber>; // number
🔹 Extracting Array Element Types
Get the type of array elements:
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type StringArray = string[];
type NumberArray = number[];
type Elem1 = ArrayElement<StringArray>; // string
type Elem2 = ArrayElement<NumberArray>; // number
type Elem3 = ArrayElement<string>; // never (not an array)
// Practical usage
function getFirstElement<T extends any[]>(
arr: T
): ArrayElement<T> | undefined {
return arr[0];
}
const nums = [1, 2, 3];
const first = getFirstElement(nums); // number | undefined
🔹 Distributive Conditional Types
Apply conditions to each member of a union:
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumber = string | number;
type Result = ToArray<StringOrNumber>;
// Result: string[] | number[]
// Non-distributive version (wrap in tuple)
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<StringOrNumber>;
// Result2: (string | number)[]
// Practical example: Filter union types
type ExtractStrings<T> = T extends string ? T : never;
type Mixed = string | number | boolean;
type OnlyStrings = ExtractStrings<Mixed>; // string
🔹 Nested Conditional Types
Chain multiple conditions for complex logic:
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T1 = TypeName<"hello">; // "string"
type T2 = TypeName<42>; // "number"
type T3 = TypeName<true>; // "boolean"
type T4 = TypeName<() => void>; // "function"
type T5 = TypeName<{}>; // "object"
🔹 Practical Example: Flatten Type
Flatten nested arrays recursively:
type Flatten<T> = T extends any[]
? T[number] extends infer U
? U extends any[]
? Flatten<U>
: U
: never
: T;
type Nested = [1, [2, [3, [4]]]];
type Flat = Flatten<Nested>; // 1 | 2 | 3 | 4
type SimpleArray = number[];
type FlatSimple = Flatten<SimpleArray>; // number
type NotArray = string;
type FlatString = Flatten<NotArray>; // string
🔹 Exclude and Extract Utilities
Filter union types with conditional types:
// Exclude: Remove types from union
type MyExclude<T, U> = T extends U ? never : T;
type AllTypes = string | number | boolean;
type NoStrings = MyExclude<AllTypes, string>; // number | boolean
// Extract: Keep only matching types
type MyExtract<T, U> = T extends U ? T : never;
type OnlyNumbers = MyExtract<AllTypes, number>; // number
// Built-in utilities
type Excluded = Exclude<AllTypes, string>; // number | boolean
type Extracted = Extract<AllTypes, number>; // number
🔹 Advanced: Function Parameter Types
Extract parameter types from functions:
type GetParameters<T> = T extends (...args: infer P) => any ? P : never;
function example(name: string, age: number, active: boolean) {
return { name, age, active };
}
type Params = GetParameters<typeof example>;
// Result: [name: string, age: number, active: boolean]
// Using built-in Parameters utility
type Params2 = Parameters<typeof example>;
// Result: [name: string, age: number, active: boolean]
// Get first parameter
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any
? F
: never;
type First = FirstParam<typeof example>; // string
💡 Key Takeaways
-
Conditional types
use
T extends U ? X : Ysyntax -
Use
inferkeyword to extract types within conditions - Distributive behavior applies conditions to union members
- Chain conditions for complex type logic
-
Built-in utilities like
ReturnType,Parameters,Excludeuse conditional types