TypeScript Tips: Utility Types You Should Know
TypeScript's built-in utility types can save you tons of boilerplate. Here are the ones I use constantly.
Stop Rewriting Types
One of the best parts of TypeScript is the utility types. They let you derive new types from existing ones without repetition.
Here are the ones I reach for most often.
Partial<T>
Makes all properties optional:
interface User {
id: number;
name: string;
email: string;
}
// For updates, you might not have all fields
function updateUser(id: number, updates: Partial<User>) {
// updates can have any subset of User properties
}
updateUser(1, { name: 'New Name' }); // Valid
Pick<T, K>
Select specific properties:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// For public display, we don't want password
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
Omit<T, K>
The opposite of Pick - exclude properties:
// Same result as above, different approach
type PublicUser = Omit<User, 'password' | 'createdAt'>;
Record<K, V>
Create an object type with specific keys and value types:
type Status = 'pending' | 'approved' | 'rejected';
// Map of status to count
type StatusCounts = Record<Status, number>;
const counts: StatusCounts = {
pending: 5,
approved: 10,
rejected: 2
};
Required<T>
Makes all properties required (opposite of Partial):
interface Config {
apiUrl?: string;
timeout?: number;
}
// After merging with defaults, we know all fields exist
function initializeApp(config: Required<Config>) {
// config.apiUrl is definitely defined here
}
ReturnType<T>
Extract the return type of a function:
function createUser() {
return { id: 1, name: 'Test', createdAt: new Date() };
}
// Type is { id: number; name: string; createdAt: Date }
type User = ReturnType<typeof createUser>;
Combining Them
The real power is combining these:
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
}
// For user creation, id and createdAt are auto-generated
type CreateUserInput = Omit<User, 'id' | 'createdAt'>;
// For updates, everything is optional except id
type UpdateUserInput = Partial<Omit<User, 'id'>> & Pick<User, 'id'>;
Conclusion
These utility types exist specifically so you don't have to maintain parallel type definitions. Use them!
What utility types do you use most? Any I missed that you swear by?