TypeScript Advanced Type System: Conditional Types & Inference
TypeScript's type system is more powerful than most developers realize. Beyond basic types and generics lie advanced patterns like conditional types, type inference, and mapped types that enable building robust, self-documenting APIs.
Conditional Types: Type-Level Logic
Conditional types allow you to select types based on conditions, creating polymorphic type logic.
Basic Conditional Type
type IsString<T> = T extends string ? true : false; type A = IsString<"hello">; // true type B = IsString<42>; // false
Extracting Union Types
type Flatten<T> = T extends Array<infer U> ? U : T; type Str = Flatten<string[]>; // string type Num = Flatten<number>; // number
Type Inference with infer
The infer keyword allows you to extract and infer types from complex structures.
// Extract function return type type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type MyFunc = (x: number) => string; type Result = ReturnType<MyFunc>; // string // Extract Promise resolved value type Unwrap<T> = T extends Promise<infer U> ? U : T; type PromiseString = Unwrap<Promise<string>>; // string type PlainNumber = Unwrap<number>; // number
Mapped Types for Transformations
Mapped types iterate over object keys and transform their values.
// Make all properties readonly type Readonly<T> = { readonly [K in keyof T]: T[K]; }; // Make all properties optional type Partial<T> = { [K in keyof T]?: T[K]; }; // Convert all properties to getters type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; interface User { name: string; age: number; } type UserGetters = Getters<User>; // { // getName: () => string; // getAge: () => number; // }
Distribution in Conditional Types
Conditional types distribute over union types automatically.
type ToArray<T> = T extends any ? T[] : never; type StrOrNum = ToArray<string | number>; // (string | number)[] - NOT string[] | number[] // To prevent distribution, wrap in tuples type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
Advanced Pattern: Deep Partial
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]>; } : T; interface Config { app: { name: string; port: number; database: { host: string; port: number; }; }; } type DeepPartialConfig = DeepPartial<Config>;
Building Type-Safe Builders
type Builder<T> = { [K in keyof T]-?: (value: T[K]) => Builder<Omit<T, K>>; } & { build(): T; }; function createBuilder<T>(initial: T): Builder<T> { // Implementation creates fluent API return null as any; } const user = createBuilder({ name: '', email: '' }) .name('John') .email('john@example.com') .build();
Performance Implications
Complex conditional types can impact compilation time. Use type aliases for recursive patterns sparingly and consider breaking complex type logic into smaller, reusable units.
SEO Keywords
Advanced TypeScript patterns enable building scalable, type-safe applications. Master conditional types, type inference, mapped types, and distribution patterns to write maintainable code that scales with your project.
Mastering TypeScript's advanced type system transforms how you build robust, self-documenting APIs and applications.