TypeScript provides a rich set of built-in utility types that transform and manipulate other types. Understanding these tools lets you write more expressive and type-safe code without reinventing the wheel.
Property Modification Types
These types modify the optionality, mutability, and requirement of object properties:
interface User {
id: number;
name: string;
email?: string;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
type RequiredUser = Required<User>;
// { id: number; name: string; email: string; }
type ImmutableUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email?: string; }
| Type | Effect | Common Use |
|---|---|---|
Partial<T> | All properties optional | PATCH request bodies |
Required<T> | All properties required | Form submission validation |
Readonly<T> | All properties read-only | Configuration objects |
Partial is especially useful for API update operations where clients should be able to send a subset of fields:
async function updateUser(id: number, changes: Partial<User>): Promise<User> {
const res = await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(changes),
});
return res.json();
}
Property Selection Types
Pick and Omit allow selecting or excluding specific properties from a type:
type UserView = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; }
Omit is ideal for removing sensitive fields:
type SafeUser = Omit<User, 'password' | 'token'>;
These types compose well with intersection types for fine-grained control:
type UserProfile = Pick<User, 'name' | 'email'> & { bio?: string };
Union Filtering Types
Extract and Exclude filter union members based on type compatibility:
type Status = 'idle' | 'loading' | 'success' | 'error';
type ActiveStatus = Exclude<Status, 'idle'>;
// 'loading' | 'success' | 'error'
type ApiEvent =
| { type: 'user.created'; user: User }
| { type: 'user.deleted'; id: number }
| { type: 'error'; message: string };
type UserEvents = Extract<ApiEvent, { type: `user.${string}` }>;
// { type: 'user.created'; user: User } | { type: 'user.deleted'; id: number }
NonNullable<T> strips null and undefined from a type:
type Maybe = string | null | undefined;
type Definite = NonNullable<Maybe>; // string
Object Construction Types
Record<K, T> creates an object type with specified key and value types:
type UserMap = Record<string, User>;
type RolePermissions = Record<'admin' | 'editor' | 'viewer', string[]>;
const permissions: RolePermissions = {
admin: ['create', 'read', 'update', 'delete'],
editor: ['create', 'read', 'update'],
viewer: ['read'],
};
Function and Class Types
These types extract information from function signatures:
type Fn = (a: string, b: number) => boolean;
type Params = Parameters<Fn>; // [string, number]
type Ret = ReturnType<Fn>; // boolean
type FirstParam = Params[0]; // string
Awaited<T> unwraps Promise types recursively:
type AsyncResult = Awaited<Promise<Promise<string>>>; // string
InstanceType<T> extracts the instance type from a constructor:
class Service { /* ... */ }
type ServiceInstance = InstanceType<typeof Service>; // Service
Composition Patterns
Combining utility types creates expressive type definitions:
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type UserUpdate = Optional<User, 'email'>;
// id and name required, email optional
For deep partial objects, a recursive mapped type does the job:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
Mastering TypeScript utility types reduces boilerplate, improves type safety, and makes your code more expressive. Start with Partial, Pick, and Record, then gradually incorporate more advanced types as your type-level programming needs grow.
