TypeScript ships with a set of built-in utility types that make working with existing types far more ergonomic. One of the most useful is Pick<Type, Keys> β a simple but powerful tool for carving out a subset of properties from a larger type.
What Is Pick?
Pick<Type, Keys> constructs a new type by selecting a specific set of properties (Keys) from an existing type (Type). Think of it as a projection: you have a big object shape, and you only want a slice of it.
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
A Practical Example
Suppose you have a User type across your application:
type User = {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
role: 'admin' | 'editor' | 'viewer';
};
When building a public-facing API response, you never want to expose passwordHash. And a user profile card in the UI might only care about id, name, and email. Instead of duplicating the shape or manually re-declaring those fields, reach for Pick:
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// Equivalent to:
// type PublicUser = {
// id: number;
// name: string;
// email: string;
// };
Now PublicUser stays in sync with User automatically. If you rename name to fullName on User, TypeScript will immediately flag any Pick references to 'name' as an error.
Real-World Use Cases
Form State
When a form only edits a subset of a model's fields, Pick keeps the shape accurate without duplication:
type UpdateProfileForm = Pick<User, 'name' | 'email'>;
function submitProfileUpdate(data: UpdateProfileForm): Promise<void> {
return api.patch('/profile', data);
}
Component Props
Vue and React components often need just a few fields from a larger model. Pick makes this explicit in the prop type:
type AvatarProps = Pick<User, 'id' | 'name'> & {
size?: 'sm' | 'md' | 'lg';
};
Service Layer Boundaries
When passing data between services, Pick enforces that only the relevant fields cross the boundary β a lightweight form of data encapsulation:
type EmailPayload = Pick<User, 'name' | 'email'>;
function sendWelcomeEmail(user: EmailPayload): void {
mailer.send({
to: user.email,
subject: `Welcome, ${user.name}!`,
});
}
Combining with Other Utility Types
Pick composes cleanly with other utilities. For example, Partial<Pick<...>> gives you an optional subset β perfect for PATCH request bodies:
type PatchUserRequest = Partial<Pick<User, 'name' | 'email' | 'role'>>;
Pick vs Omit
Pick and Omit are mirror images of each other:
| Utility | Approach | Best when⦠|
|---|---|---|
Pick<Type, Keys> |
Allowlist β name what you want | The desired set is small |
Omit<Type, Keys> |
Denylist β name what you don't want | The excluded set is small |
If you want all fields except one or two, Omit is more concise. If you want just a handful of fields from a large type, Pick is cleaner.
// These are equivalent when User has exactly these five fields:
type WithoutPassword = Omit<User, 'passwordHash'>;
type SafeFields = Pick<User, 'id' | 'name' | 'email' | 'createdAt' | 'role'>;
// Prefer Omit here β fewer keys to list
Key Takeaways
Pick<Type, Keys>creates a new type with only the named properties fromType.- The
Keysargument is validated againstkeyof Typeat compile time β typos are caught immediately. - It keeps derived types in sync with their source; renames and removals surface as errors rather than silent mismatches.
- It composes well with
Partial,Required,Readonly, andOmitfor expressive, minimal type definitions.
Whenever you find yourself re-declaring a handful of fields that already exist on another type, Pick is almost certainly the right tool.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.