Conditional Types
HardConditional types select one of two types based on a condition, similar to a ternary operator but at the type level. The syntax `T extends U ? X : Y` checks if T is assignable to U and resolves to X or Y accordingly. When combined with `infer`, conditional types can extract types from complex structures, enabling powerful type-level programming.
Interactive Visualization
Key Points
- Syntax: `T extends U ? TrueType : FalseType` — a type-level ternary
- Conditional types distribute over union types when T is a bare type parameter
- The `infer` keyword declares an inner type variable to extract sub-types
- `ReturnType<T>` is implemented as a conditional type with infer
- Nested conditionals can chain multiple checks for complex logic
- Use `[T] extends [U]` (wrapped in tuple) to prevent distribution over unions
Code Examples
Basic Conditional Types
// Simple conditional type type IsString<T> = T extends string ? true : false type A = IsString<string> // true type B = IsString<number> // false type C = IsString<'hello'> // true (literal extends string) // Distributive behavior over unions type D = IsString<string | number> // true | false => boolean // Prevent distribution with tuple wrapping type IsStringStrict<T> = [T] extends [string] ? true : false type E = IsStringStrict<string | number> // false (union is not string) // Practical example: non-nullable type NonNullable<T> = T extends null | undefined ? never : T type F = NonNullable<string | null | undefined> // string
Conditional types perform type-level branching. They distribute over unions by default, meaning each member of a union is checked individually. This is how Exclude and Extract work.
The `infer` Keyword
// Extract return type of a function type MyReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never type Fn = (x: string) => number type Result = MyReturnType<Fn> // number // Extract element type of an array type ElementOf<T> = T extends (infer E)[] ? E : never type Nums = ElementOf<number[]> // number // Extract promise value type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T type Val = Awaited<Promise<Promise<string>>> // string (recursive!) // Extract first argument type type FirstArg<T> = T extends (first: infer F, ...rest: unknown[]) => unknown ? F : never type FA = FirstArg<(name: string, age: number) => void> // string
The `infer` keyword lets you "capture" a type from within a conditional check. It is like pattern matching — TypeScript extracts the type that fits in the infer position.
Advanced Conditional Patterns
// Flatten nested arrays to a specific depth type Flatten<T> = T extends (infer E)[] ? Flatten<E> : T type Deep = number[][][] type Flat = Flatten<Deep> // number // Type-safe event emitter type EventHandler<T> = T extends void ? () => void : (data: T) => void interface Events { login: { userId: string } logout: void error: Error } type LoginHandler = EventHandler<Events['login']> // (data: { userId: string }) => void type LogoutHandler = EventHandler<Events['logout']> // () => void // String manipulation at type level type CamelToSnake<S extends string> = S extends `${infer Head}${infer Tail}` ? Tail extends Uncapitalize<Tail> ? `${Lowercase<Head>}${CamelToSnake<Tail>}` : `${Lowercase<Head>}_${CamelToSnake<Tail>}` : S
Conditional types can be recursive and can perform string manipulation at the type level. These patterns are common in library types for things like API response transformation.
Common Mistakes
- Not understanding distributive behavior — `IsString<string | number>` produces `boolean`, not `false`
- Forgetting that `infer` only works inside the `extends` clause of a conditional type
- Creating deeply recursive conditional types that hit TypeScript recursion limits
- Confusing the type-level ternary with a runtime ternary — conditional types run at compile time only
- Using conditional types when a simpler mapped type or generic constraint would suffice
Interview Tips
- Be able to implement ReturnType and Parameters from scratch using infer
- Explain distributive conditional types and how to prevent distribution
- Show understanding of infer as "type-level pattern matching"
- Know when conditional types are overkill vs when they are the right tool