TypeScript Utility Types: Pick, Omit, and Partial Explained
Every API needs a slightly different shape of the same data: the full User, a public-safe User without the password hash, a form that only updates two fields. Copy-pasting the interface for each case means they drift apart the moment one gets updated. TypeScript's built-in utility types solve this by deriving new types from one source of truth.
The Problem
interface User {
id: string
name: string
email: string
passwordHash: string
createdAt: Date
}
// Duplicated, drifts out of sync with User over time
interface PublicUser {
id: string
name: string
email: string
createdAt: Date
}
Every time User changes, someone has to remember to update PublicUser too. They won't.
Partial<T>: Make Every Field Optional
Perfect for update payloads, where the caller only sends the fields that changed.
function updateUser(id: string, changes: Partial<User>) {
// changes might be { name: 'New Name' } or { email: '...' }
return db.users.update(id, changes)
}
Pick<T, K>: Select a Subset of Fields
Use it when you only need a few fields from a larger type — a preview card, a dropdown option.
type UserPreview = Pick<User, 'id' | 'name'>
// { id: string; name: string }
Omit<T, K>: Exclude Specific Fields
The mirror image of Pick — keep everything except what you list. Ideal for stripping sensitive fields before sending a response.
type PublicUser = Omit<User, 'passwordHash'>
// { id: string; name: string; email: string; createdAt: Date }
Now PublicUser always tracks User automatically. Add a field to User, and it shows up in PublicUser without touching this line.
Combining Them
Utility types compose. A "rename form" that only lets you edit the name:
type RenameForm = Partial<Pick<User, 'name'>>
// { name?: string }
How the Types Derive From One Source
When to Use Which
- Partial<T>: PATCH/update payloads, optional config objects, form state before submission.
- Pick<T, K>: previews, dropdown options, anywhere you need a deliberately small, explicit subset.
- Omit<T, K>: stripping sensitive or internal fields (passwords, internal IDs) while keeping everything else in sync automatically.
Common Pitfalls
- Omit with a renamed/removed key silently succeeds — TypeScript won't error if you omit a key that was already removed from the base type, so typos in the key list can go unnoticed. Double-check the literal keys against the source type.
- Partial hides genuinely required fields — a
Partial<User>update function can't tell "the caller forgotid" from "the caller doesn't want to changeid." Keep required identifiers (likeid) outside thePartialwrapper:{ id: string } & Partial<Omit<User, 'id'>>. - Reaching for
Record<string, unknown>instead ofPick/Omitthrows away type safety entirely — utility types keep the connection to the original shape, a looseRecorddoesn't.
Derive, don't duplicate. If two types describe the same entity, one of them should be built from the other with
Pick,Omit, orPartial— not maintained by hand.

