TypeScript

TypeScript Advanced Types: Mastering Conditional and Mapped Types

Deep dive into TypeScript's advanced type system including conditional types, mapped types, template literal types, and utility types for building type-safe applications.

I
Isaiah Clifford Opoku
Dec 15, 20245 min read
#typescript#types#javascript
5 min
reading time
TypeScript Advanced Types: Mastering Conditional and Mapped Types

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:

typescript
1type 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:

typescript
1// 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:

typescript
1type 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:

typescript
1// 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:

typescript
1// 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:

typescript
1// 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:

typescript
1// 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:

typescript
1// 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.

Related Technologies

Technologies and tools featured in this article

#

typescript

#

types

#

javascript

#

frontend

#

programming

5
Technologies
TypeScript
Category
5m
Read Time
Back to all posts

Found this helpful?

I'm posting .NET and software engineering content on multiple Social Media platforms.

If you enjoyed this article and want to see more content like this, consider subscribing to my newsletter or following me on social media for the latest updates.