TypeScript provides powerful utility types for common type transformations. Here's a comprehensive guide to using them effectively.
Partial and Required#
1interface User {
2 id: number;
3 name: string;
4 email: string;
5 age?: number;
6}
7
8// Make all properties optional
9type PartialUser = Partial<User>;
10// { id?: number; name?: string; email?: string; age?: number; }
11
12// Make all properties required
13type RequiredUser = Required<User>;
14// { id: number; name: string; email: string; age: number; }
15
16// Practical use: update functions
17function updateUser(id: number, updates: Partial<User>): User {
18 const user = getUser(id);
19 return { ...user, ...updates };
20}
21
22updateUser(1, { name: 'John' }); // Only update nameReadonly and Mutable#
1interface Config {
2 apiUrl: string;
3 timeout: number;
4 retries: number;
5}
6
7// Make all properties readonly
8type ReadonlyConfig = Readonly<Config>;
9// { readonly apiUrl: string; readonly timeout: number; readonly retries: number; }
10
11const config: ReadonlyConfig = {
12 apiUrl: 'https://api.example.com',
13 timeout: 5000,
14 retries: 3,
15};
16
17// config.timeout = 10000; // Error: Cannot assign to 'timeout'
18
19// Create mutable version (custom utility)
20type Mutable<T> = {
21 -readonly [P in keyof T]: T[P];
22};
23
24type MutableConfig = Mutable<ReadonlyConfig>;Pick and Omit#
1interface Article {
2 id: number;
3 title: string;
4 content: string;
5 author: string;
6 createdAt: Date;
7 updatedAt: Date;
8}
9
10// Pick specific properties
11type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;
12// { id: number; title: string; author: string; }
13
14// Omit specific properties
15type ArticleInput = Omit<Article, 'id' | 'createdAt' | 'updatedAt'>;
16// { title: string; content: string; author: string; }
17
18// Combine for complex types
19type EditableArticle = Omit<Article, 'id' | 'createdAt'> & { id: number };Record#
1// Create object type with specific keys
2type Status = 'pending' | 'approved' | 'rejected';
3type StatusMessages = Record<Status, string>;
4
5const messages: StatusMessages = {
6 pending: 'Awaiting review',
7 approved: 'Application approved',
8 rejected: 'Application rejected',
9};
10
11// Dynamic keys
12type UserRoles = Record<string, string[]>;
13
14const roles: UserRoles = {
15 admin: ['read', 'write', 'delete'],
16 user: ['read'],
17 guest: [],
18};
19
20// Numeric keys
21type IndexedItems = Record<number, { name: string }>;Extract and Exclude#
1type AllTypes = string | number | boolean | null | undefined;
2
3// Extract types that extend a condition
4type Primitives = Extract<AllTypes, string | number | boolean>;
5// string | number | boolean
6
7// Exclude types that extend a condition
8type NonNullTypes = Exclude<AllTypes, null | undefined>;
9// string | number | boolean
10
11// Practical use: event types
12type MouseEvents = 'click' | 'dblclick' | 'mousedown' | 'mouseup';
13type KeyboardEvents = 'keydown' | 'keyup' | 'keypress';
14type AllEvents = MouseEvents | KeyboardEvents;
15
16type OnlyMouseEvents = Extract<AllEvents, MouseEvents>;
17type WithoutMouseEvents = Exclude<AllEvents, MouseEvents>;NonNullable#
1type MaybeString = string | null | undefined;
2type DefinitelyString = NonNullable<MaybeString>;
3// string
4
5// Practical use
6function processValue<T>(value: T): NonNullable<T> {
7 if (value === null || value === undefined) {
8 throw new Error('Value cannot be null or undefined');
9 }
10 return value as NonNullable<T>;
11}
12
13const result = processValue<string | null>('hello');
14// result is string, not string | nullReturnType and Parameters#
1function createUser(name: string, age: number): { id: number; name: string; age: number } {
2 return { id: Date.now(), name, age };
3}
4
5// Get return type
6type User = ReturnType<typeof createUser>;
7// { id: number; name: string; age: number }
8
9// Get parameter types
10type CreateUserParams = Parameters<typeof createUser>;
11// [string, number]
12
13// Useful for wrapping functions
14function loggedCreateUser(...args: Parameters<typeof createUser>): ReturnType<typeof createUser> {
15 console.log('Creating user with:', args);
16 return createUser(...args);
17}InstanceType and ConstructorParameters#
1class ApiClient {
2 constructor(baseUrl: string, timeout: number) {
3 // ...
4 }
5}
6
7// Get instance type
8type Client = InstanceType<typeof ApiClient>;
9// ApiClient
10
11// Get constructor parameters
12type ClientParams = ConstructorParameters<typeof ApiClient>;
13// [string, number]
14
15// Factory function
16function createClient(...args: ConstructorParameters<typeof ApiClient>): InstanceType<typeof ApiClient> {
17 return new ApiClient(...args);
18}ThisParameterType and OmitThisParameter#
1function greet(this: { name: string }, greeting: string): string {
2 return `${greeting}, ${this.name}!`;
3}
4
5// Get 'this' type
6type GreetThis = ThisParameterType<typeof greet>;
7// { name: string }
8
9// Remove 'this' parameter
10type GreetWithoutThis = OmitThisParameter<typeof greet>;
11// (greeting: string) => string
12
13// Bind function
14const boundGreet: GreetWithoutThis = greet.bind({ name: 'World' });
15boundGreet('Hello'); // 'Hello, World!'Awaited#
1type PromiseString = Promise<string>;
2type ResolvedString = Awaited<PromiseString>;
3// string
4
5// Works with nested promises
6type NestedPromise = Promise<Promise<Promise<number>>>;
7type ResolvedNumber = Awaited<NestedPromise>;
8// number
9
10// Useful for async function return types
11async function fetchData(): Promise<{ data: string }> {
12 return { data: 'result' };
13}
14
15type FetchResult = Awaited<ReturnType<typeof fetchData>>;
16// { data: string }Uppercase, Lowercase, Capitalize, Uncapitalize#
1type Greeting = 'hello world';
2
3type Upper = Uppercase<Greeting>; // 'HELLO WORLD'
4type Lower = Lowercase<'HELLO WORLD'>; // 'hello world'
5type Cap = Capitalize<Greeting>; // 'Hello world'
6type Uncap = Uncapitalize<'Hello'>; // 'hello'
7
8// Practical use: event handlers
9type EventName = 'click' | 'focus' | 'blur';
10type HandlerName = `on${Capitalize<EventName>}`;
11// 'onClick' | 'onFocus' | 'onBlur'
12
13type Handlers = {
14 [K in EventName as `on${Capitalize<K>}`]: (event: Event) => void;
15};
16// { onClick: ..., onFocus: ..., onBlur: ... }Combining Utility Types#
1interface Entity {
2 id: number;
3 createdAt: Date;
4 updatedAt: Date;
5}
6
7interface User extends Entity {
8 name: string;
9 email: string;
10 role: 'admin' | 'user';
11}
12
13// Create input type (without auto-generated fields)
14type CreateUserInput = Omit<User, keyof Entity>;
15// { name: string; email: string; role: 'admin' | 'user' }
16
17// Create update type (all fields optional except id)
18type UpdateUserInput = Partial<Omit<User, 'id'>> & Pick<User, 'id'>;
19// { id: number; name?: string; email?: string; ... }
20
21// Readonly entity
22type ReadonlyUser = Readonly<User>;
23
24// Pick with partial
25type PartialPick<T, K extends keyof T> = Partial<Pick<T, K>>;
26type OptionalUserInfo = PartialPick<User, 'name' | 'email'>;
27// { name?: string; email?: string }Custom Utility Types#
1// Deep Partial
2type DeepPartial<T> = {
3 [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
4};
5
6// Deep Readonly
7type DeepReadonly<T> = {
8 readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
9};
10
11// Nullable
12type Nullable<T> = T | null;
13
14// Optional keys
15type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
16
17// Required keys
18type RequiredKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
19
20// Pick by type
21type PickByType<T, U> = {
22 [P in keyof T as T[P] extends U ? P : never]: T[P];
23};
24
25// Usage
26interface Mixed {
27 id: number;
28 name: string;
29 active: boolean;
30 count: number;
31}
32
33type StringProps = PickByType<Mixed, string>;
34// { name: string }
35
36type NumberProps = PickByType<Mixed, number>;
37// { id: number; count: number }Best Practices#
Common Patterns:
✓ Partial for update functions
✓ Pick/Omit for API responses
✓ Record for dictionaries
✓ ReturnType for function wrappers
Type Safety:
✓ Use Readonly for constants
✓ Use NonNullable after null checks
✓ Combine utilities for complex types
✓ Create custom utilities for reuse
Avoid:
✗ Over-nesting utility types
✗ Complex type gymnastics
✗ Ignoring readability
✗ Reinventing built-in types
Performance:
✓ Keep type complexity manageable
✓ Use type aliases for readability
✓ Document complex transformations
✓ Test types with assertions
Conclusion#
TypeScript's utility types provide powerful, composable tools for type transformations. Use Partial and Required for optionality, Pick and Omit for property selection, Record for dictionaries, and Extract/Exclude for union manipulation. Combine them to create precise types for your domain, and build custom utilities for patterns specific to your codebase. These tools enable type-safe APIs while keeping your code DRY.