TypeScript's type system is incredibly powerful, offering advanced features that go far beyond basic type annotations. Let's explore the most sophisticated type-level programming techniques.
Conditional Types
Conditional types allow you to create types that depend on conditions, similar to ternary operators in JavaScript:
typescript1type IsString<T> = T extends string ? true : false; 2 3type Test1 = IsString<string>; // true 4type Test2 = IsString<number>; // false 5 6// Practical example: Extract return type 7type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; 8 9function getUserData(): { id: number; name: string } { 10 return { id: 1, name: 'John' }; 11} 12 13type UserData = ReturnType<typeof getUserData>; // { id: number; name: string }
Mapped Types
Transform existing types by mapping over their properties:
typescript1// Make all properties optional 2type Partial<T> = { 3 [P in keyof T]?: T[P]; 4}; 5 6// Make all properties readonly 7type Readonly<T> = { 8 readonly [P in keyof T]: T[P]; 9}; 10 11// Pick specific properties 12type Pick<T, K extends keyof T> = { 13 [P in K]: T[P]; 14}; 15 16interface User { 17 id: number; 18 name: string; 19 email: string; 20 password: string; 21} 22 23// Usage examples 24type PartialUser = Partial<User>; 25type PublicUser = Pick<User, 'id' | 'name' | 'email'>; 26type ReadonlyUser = Readonly<User>;
Template Literal Types
Create sophisticated string types using template literals:
typescript1type EventName<T extends string> = `on${Capitalize<T>}`; 2type ApiEndpoint<T extends string> = `/api/${T}`; 3 4type UserEvents = EventName<'click' | 'hover' | 'focus'>; 5// "onClick" | "onHover" | "onFocus" 6 7type UserEndpoints = ApiEndpoint<'users' | 'posts' | 'comments'>; 8// "/api/users" | "/api/posts" | "/api/comments" 9 10// Advanced pattern matching 11type ExtractRouteParams<T extends string> = 12 T extends `${string}:${infer Param}/${infer Rest}` 13 ? Param | ExtractRouteParams<Rest> 14 : T extends `${string}:${infer Param}` 15 ? Param 16 : never; 17 18type RouteParams = ExtractRouteParams<'/users/:id/posts/:postId'>; 19// "id" | "postId"
Advanced Utility Types
Create powerful utility types for common patterns:
typescript1// Deep partial - makes nested properties optional 2type DeepPartial<T> = { 3 [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; 4}; 5 6// Required recursive - makes all properties required 7type DeepRequired<T> = { 8 [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P]; 9}; 10 11// Extract function parameters 12type Parameters<T extends (...args: any) => any> = T extends ( 13 ...args: infer P 14) => any 15 ? P 16 : never; 17 18// Create union from object values 19type ValueOf<T> = T[keyof T]; 20 21interface APIResponses { 22 users: User[]; 23 posts: Post[]; 24 comments: Comment[]; 25} 26 27type ResponseType = ValueOf<APIResponses>; // User[] | Post[] | Comment[]
Recursive Types
Build complex recursive type structures:
typescript1// JSON type representation 2type JSONValue = string | number | boolean | null | JSONObject | JSONArray; 3 4interface JSONObject { 5 [key: string]: JSONValue; 6} 7 8interface JSONArray extends Array<JSONValue> {} 9 10// Tree structure 11interface TreeNode<T> { 12 value: T; 13 children?: TreeNode<T>[]; 14} 15 16// Flatten nested arrays 17type FlatArray<T> = T extends readonly (infer U)[] 18 ? U extends readonly any[] 19 ? FlatArray<U> 20 : U 21 : T; 22 23type NestedNumbers = number[][][]; 24type FlattedNumbers = FlatArray<NestedNumbers>; // number
Type Guards and Discriminated Unions
Create runtime type safety with type guards:
typescript1// Discriminated union 2interface LoadingState { 3 status: 'loading'; 4} 5 6interface SuccessState { 7 status: 'success'; 8 data: any; 9} 10 11interface ErrorState { 12 status: 'error'; 13 error: string; 14} 15 16type AsyncState = LoadingState | SuccessState | ErrorState; 17 18// Type guard functions 19function isSuccess(state: AsyncState): state is SuccessState { 20 return state.status === 'success'; 21} 22 23function isError(state: AsyncState): state is ErrorState { 24 return state.status === 'error'; 25} 26 27// Usage with type narrowing 28function handleState(state: AsyncState) { 29 if (isSuccess(state)) { 30 // TypeScript knows state is SuccessState 31 console.log(state.data); 32 } else if (isError(state)) { 33 // TypeScript knows state is ErrorState 34 console.log(state.error); 35 } 36}
Advanced Generic Constraints
Use sophisticated constraints for type safety:
typescript1// Constrain to object with specific key 2interface HasId { 3 id: string | number; 4} 5 6function updateEntity<T extends HasId>(entity: T, updates: Partial<T>): T { 7 return { ...entity, ...updates }; 8} 9 10// Constrain to specific string patterns 11type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; 12 13function apiCall<M extends HttpMethod>( 14 method: M, 15 url: string, 16 body?: M extends 'GET' ? never : any 17) { 18 // Implementation 19} 20 21// Usage 22apiCall('GET', '/users'); // ✅ Valid 23apiCall('POST', '/users', { name: 'John' }); // ✅ Valid 24// apiCall('GET', '/users', { data: 'invalid' }); // ❌ Error
Practical Example: Form Validation
Combine multiple advanced types for a real-world scenario:
typescript1// Base validation types 2type ValidationRule<T> = (value: T) => string | null; 3type FieldValidators<T> = { 4 [K in keyof T]?: ValidationRule<T[K]>[]; 5}; 6 7// Form state management 8interface FormState<T> { 9 values: T; 10 errors: Partial<Record<keyof T, string>>; 11 touched: Partial<Record<keyof T, boolean>>; 12} 13 14// Form configuration 15interface FormConfig<T> { 16 initialValues: T; 17 validators?: FieldValidators<T>; 18 onSubmit: (values: T) => Promise<void>; 19} 20 21// Advanced form hook 22function useForm<T extends Record<string, any>>(config: FormConfig<T>) { 23 const [state, setState] = useState<FormState<T>>({ 24 values: config.initialValues, 25 errors: {}, 26 touched: {}, 27 }); 28 29 const validate = (field: keyof T, value: T[keyof T]) => { 30 const validators = config.validators?.[field]; 31 if (!validators) return null; 32 33 for (const validator of validators) { 34 const error = validator(value); 35 if (error) return error; 36 } 37 return null; 38 }; 39 40 const updateField = <K extends keyof T>(field: K, value: T[K]) => { 41 const error = validate(field, value); 42 setState(prev => ({ 43 ...prev, 44 values: { ...prev.values, [field]: value }, 45 errors: { ...prev.errors, [field]: error }, 46 touched: { ...prev.touched, [field]: true }, 47 })); 48 }; 49 50 return { 51 ...state, 52 updateField, 53 isValid: Object.values(state.errors).every(error => !error), 54 }; 55}
Conclusion
TypeScript's advanced type system enables incredibly sophisticated type-level programming. These features help catch errors at compile time, improve code documentation, and create better developer experiences through intelligent autocomplete and refactoring support.
Master these patterns to build more robust, maintainable TypeScript applications that leverage the full power of the type system.