Why Utility Types Matter
One of TypeScript's most powerful — and often underused — features is its collection of built-in utility types. These are generic types provided by TypeScript itself that let you transform existing types into new ones, saving you from duplicating interface definitions and keeping your codebase DRY.
The Core Utility Types You Need to Know
1. Partial<T>
Makes all properties of a type optional. Perfect for update payloads where only some fields change.
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, changes: Partial<User>) {
// 'changes' can have any subset of User fields
}
2. Required<T>
The opposite of Partial — makes all optional properties required. Useful for validating that a fully-populated object is present.
3. Pick<T, K>
Creates a new type containing only the specified keys from T. Great for creating focused view models from larger data types.
type UserPreview = Pick<User, 'id' | 'name'>;
// Result: { id: number; name: string }
4. Omit<T, K>
The complement to Pick — creates a type with certain keys removed. Commonly used to strip sensitive fields like passwords before sending data to the client.
interface UserWithPassword extends User {
passwordHash: string;
}
type SafeUser = Omit<UserWithPassword, 'passwordHash'>;
5. Readonly<T>
Makes all properties of a type read-only, preventing accidental mutation — ideal for configuration objects and Redux state.
6. Record<K, V>
Constructs an object type with keys of type K and values of type V. More expressive than a plain index signature.
type StatusMap = Record<'success' | 'error' | 'pending', string>;
const messages: StatusMap = {
success: 'Operation completed',
error: 'Something went wrong',
pending: 'Loading...',
};
7. ReturnType<T> and Parameters<T>
These extract the return type or parameter types of a function, respectively. Invaluable when working with third-party libraries where you don't control the type definitions.
function fetchUser(id: number): Promise<User> { /* ... */ }
type FetchResult = ReturnType<typeof fetchUser>; // Promise<User>
type FetchArgs = Parameters<typeof fetchUser>; // [number]
Combining Utility Types
The real power comes from composing these types:
// A form state: all fields optional, all read-only
type FormState = Readonly<Partial<User>>;
Conditional Types for Advanced Transformations
Beyond the built-ins, TypeScript supports conditional types — type-level if/else logic:
type NonNullable<T> = T extends null | undefined ? never : T;
This pattern is used internally to build many of the utility types above, and you can build your own custom utilities using the same approach.
Quick Reference Table
| Utility Type | What It Does | Common Use Case |
|---|---|---|
| Partial<T> | All props optional | PATCH request body |
| Required<T> | All props required | Validated form data |
| Pick<T, K> | Keep only K keys | View models, projections |
| Omit<T, K> | Remove K keys | Strip sensitive fields |
| Readonly<T> | Immutable props | Config, Redux state |
| Record<K, V> | Typed key-value map | Lookup tables, enums |
| ReturnType<T> | Extract return type | Inferring library types |
Conclusion
TypeScript's utility types are one of its most practical features. By leveraging them, you avoid duplicated interface definitions, communicate intent more clearly, and let the compiler catch errors that would otherwise only surface at runtime. Start with Partial, Pick, and Omit — you'll find uses for them in nearly every project.