Pick vs Omit.
Whitelist vs blacklist.
Both take a type and produce a smaller one. Pick keeps only the keys you list. Omit removes the keys you list. They sound similar but they scale differently — and choosing the right one determines whether your type stays robust or silently breaks when the base type grows. Here's the difference and the decision rule.
Short answer: Pick is a whitelist — keeps only keys K. Omit is a blacklist — removes keys K. Use Omit when you're excluding specific fields (passwords, IDs, timestamps) from an otherwise complete type. Use Pick when the result is genuinely a small subset (2-3 fields for a preview or list item). The pattern that scales: Omit for "most of the shape, minus a few"; Pick for "only a few specific fields."
✅ Solution 1 — Omit (the safe default)
Excludes specific fields — new fields are automatically included.
When you have a complete type and want a version without specific fields, Omit is right. The key advantage over Pick: if you add new fields to the base type later, the Omit version automatically includes them. No silent gaps. This is why Omit is the safer default for exposing types.
// ─── The base type ─────────────────────────── interface User { id: string; name: string; email: string; password: string; createdAt: Date; role: 'admin' | 'user'; } // ─── Omit: remove sensitive fields ─────────── type PublicUser = Omit'password'>; // { id, name, email, createdAt, role } — no password type SafeUser = Omit 'password' | 'createdAt'>; // { id, name, email, role } // ─── Omit scales as the base grows ─────────── // Add a new field to User: // User { ..., avatar?: string } // // PublicUser automatically includes it — no code change. // A Pick-based version would silently miss it. interface User2 { id: string; name: string; email: string; password: string; avatar?: string; // ← new field } type PublicUser2 = Omit 'password'>; // { id, name, email, avatar? } — avatar is included automatically ✅ // ─── Omit for API payloads ─────────────────── type CreateUserInput = Omit 'id' | 'createdAt'>; // Server generates id and createdAt — client sends the rest function createUser(input: CreateUserInput): User { return { ...input, id: crypto.randomUUID(), createdAt: new Date() }; } // ─── Omit for PATCH bodies ─────────────────── type UpdateUserInput = Partial<Omit 'id' | 'createdAt'>>; // { name?, email?, password?, role? } — any subset function updateUser(id: string, patch: UpdateUserInput): void { console.log('PATCH', id, patch); } // ─── The rule ──────────────────────────────── // Omit is safer because: // - New fields are included automatically // - You only think about what's excluded // - The base type is the source of truth // // Pick requires maintenance: // - Every new field must be added manually // - Easy to forget when the base type grows console.log(createUser({ name: 'Alice', email: 'a@x.com', password: 'x', role: 'user' })); updateUser('1', { name: 'Alicia' });
function createUser(input) { return { ...input, id: crypto.randomUUID(), createdAt: new Date() }; } function updateUser(id, patch) { console.log('PATCH', id, patch); } console.log(createUser({ name: 'Alice', email: 'a@x.com', password: 'x', role: 'user' })); updateUser('1', { name: 'Alicia' });
Blacklist semantics
Omit — remove these keys. Everything else is preserved. The base type is the source of truth.
Scales with the base
Add a field to User, and every Omit picks it up automatically. New fields aren't forgotten.
Safe default
For exposing types (public user, API response), Omit is safer — you can only accidentally include something by not thinking about it.
Composes with Partial
Partial — any subset of non-immutable fields. The standard PATCH body type.
✅ Solution 2 — Pick (when the subset is small)
Explicit whitelist — good for previews and minimal shapes.
Pick is the opposite: list exactly which keys you want. It's not always the wrong choice — for a genuine preview, a list item, or a minimal API shape with 2-3 fields, Pick is clearer than Omit. The rule: if the resulting type has significantly fewer fields than the base, Pick. If the resulting type has most of the base fields, Omit.
interface User { id: string; name: string; email: string; password: string; avatar: string; role: 'admin' | 'user'; createdAt: Date; } // ─── Pick: only 2 fields ───────────────────── type UserPreview = Pick'id' | 'name'>; // { id: string; name: string } // Clearer than Omit const preview: UserPreview = { id: '1', name: 'Alice' }; // ─── Pick: list item ───────────────────────── type UserListItem = Pick 'id' | 'name' | 'avatar'>; // { id, name, avatar } — the minimum for a list row // ─── Pick: search result ───────────────────── type UserSearchResult = Pick 'id' | 'name' | 'email'>; // ─── Pick: form fields subset ──────────────── type ProfileFormFields = Pick 'name' | 'avatar'>; function updateProfile(fields: ProfileFormFields): void { console.log('update profile', fields); } updateProfile({ name: 'Alice', avatar: 'x.jpg' }); // ─── The trade-off ─────────────────────────── // // Pick is fine when: // ✅ The subset is genuinely small (2-4 fields) // ✅ The set is stable and unlikely to grow // ✅ Clarity of "these exact fields" is valuable // // Pick becomes a maintenance burden when: // ❌ The subset is most of the base type // ❌ New fields on the base should be included // ❌ You're playing whack-a-mole adding keys // ─── The pattern that scales ───────────────── // // For "most of the shape" → Omit (base minus a few) // For "just a few fields" → Pick (explicit subset) // // Mixing both is possible but rarely needed: // Pick , 'id' | 'name'> console.log(preview); updateProfile({ name: 'Bob', avatar: 'b.jpg' });
const preview = { id: '1', name: 'Alice' }; function updateProfile(fields) { console.log('update profile', fields); } updateProfile({ name: 'Alice', avatar: 'x.jpg' }); console.log(preview); updateProfile({ name: 'Bob', avatar: 'b.jpg' });
Whitelist semantics
Pick — keep only these keys. Everything else is discarded. Explicit and clear.
Great for tiny subsets
2-4 fields — Pick reads better than Omit. Pick is clearer than listing 5 omitted fields.
Requires maintenance
New fields on the base type are silently NOT included. If you meant "User minus password," and it becomes "User minus password plus avatar," Pick silently loses avatar.
Good for stability
If the subset should never change even if the base grows, Pick is the right tool — the explicit whitelist is a feature.
Default to Omit. Switch to Pick when the subset is small and stable. The decision isn't about which is "better" — both are correct for their cases. It's about intent. Do you want "the base type minus a few things" (Omit) or "just these fields, forever" (Pick)? Omit scales automatically with the base; Pick documents a fixed subset. For most API-exposure cases — PublicUser, SafeUser, UserResponse — you want Omit. For previews and minimal list items — the shape that shouldn't grow with the base — you want Pick.
✅ Solution 3 — Composition patterns
Combining Pick, Omit, Partial, and Record for real APIs.
Real API types combine utility types. The pattern is a sequence: strip what clients shouldn't see, add what they should send, then make the right things optional. Every endpoint type in a well-typed backend is built this way.
interface User { id: string; name: string; email: string; password: string; role: 'admin' | 'user'; createdAt: Date; updatedAt: Date; } // ─── Pattern 1: User without sensitive/immutable type SafeUser = Omit'password' | 'createdAt' | 'updatedAt'>; // { id, name, email, role } // ─── Pattern 2: Create input (server sets id/timestamps) type CreateUserInput = Omit 'id' | 'createdAt' | 'updatedAt'>; // { name, email, password, role } // ─── Pattern 3: Update input (all fields optional, no id/timestamps) type UpdateUserInput = Partial<Omit 'id' | 'createdAt' | 'updatedAt'>>; // { name?, email?, password?, role? } // ─── Pattern 4: Preview (list item) ────────── type UserPreview = Pick 'id' | 'name'>; // { id, name } // ─── Pattern 5: Pick + Omit for layered access // Public view — no password, no timestamps, then only basic fields type UserPublicView = Pick< Omit 'password' | 'createdAt' | 'updatedAt'>, 'id' | 'name' | 'email' >; // { id, name, email } // ─── Pattern 6: Record of Omitted types ────── type UserByRole = Record<'admin' | 'user', SafeUser[]>; // { admin: SafeUser[]; user: SafeUser[] } // ─── Pattern 7: Request body generic ───────── type RequestBody extends keyof T = never> = Partial<Omit >; type UserPatch = RequestBody 'id' | 'createdAt' | 'updatedAt'>; // Reusable pattern for any resource: // type PostPatch = RequestBody ; // type ProductPatch = RequestBody // ─── Runtime vs type ───────────────────────── // // Pick and Omit are COMPILE-TIME only. // They don't strip fields from a runtime object. const fullUser: User = { id: '1', name: 'Alice', email: 'a@x.com', password: 'secret', role: 'user', createdAt: new Date(), updatedAt: new Date() }; // Type view (no password at type level): const safeUser: SafeUser = fullUser; // ⚠️ At runtime, safeUser STILL has password! // Use destructuring to actually strip it: const { password: _, createdAt: __, updatedAt: ___, ...actualSafeUser } = fullUser; // actualSafeUser is SafeUser at both type AND runtime level function toSafeUser(u: User): SafeUser { const { password, createdAt, updatedAt, ...safe } = u; return safe; } console.log(toSafeUser(fullUser)); console.log(actualSafeUser);;
const fullUser = { id: '1', name: 'Alice', email: 'a@x.com', password: 'secret', role: 'user', createdAt: new Date(), updatedAt: new Date() }; const safeUser = fullUser; const { password: _, createdAt: __, updatedAt: ___, ...actualSafeUser } = fullUser; function toSafeUser(u) { const { password, createdAt, updatedAt, ...safe } = u; return safe; } console.log(toSafeUser(fullUser)); console.log(actualSafeUser);
Layer the operations
Strip sensitive → pick subset → make optional. Each step is one utility type. Compose as needed.
The PATCH pattern
Partial — the standard body for any PUT/PATCH endpoint. Reusable across resources.
Compose with Record
Record — typed maps of subsets. Common for grouped responses.
Compile-time only
Pick/Omit don't strip fields at runtime. To actually remove fields, destructure with the rest operator.
Pick and Omit are compile-time only — the runtime object still has all fields. If you type a variable as SafeUser = Omit and assign a full User to it, the runtime object still has password. You just can't access it via the type (without casting). For real runtime stripping — e.g. before sending over the network — use destructuring: const { password, ...safe } = user. Types describe; destructuring executes. Use both when security matters.
⚠️ Common mistakes
The Pick/Omit bugs that cost real time.
Picking every field except password — fragile. Fix: Omit. New fields included automatically.
Omit when you only want id and name. Fix: Pick.
Expecting the object to lose fields. Fix: destructure to actually strip. const { password, ...safe } = user.
Omit requires all other fields. Fix: Partial for optional updates.
Writing { id: string; name: string } by hand instead of Pick. Fix: reuse the base type.
Pick — compile error. Good! But confusing if you don't read the message. Fix: check the key names.
Omit — silently does nothing (no error for keys not in T in some versions). Fix: verify the key names match.
"I'll just Pick the fields I need" — requires maintenance every time the base grows. Fix: use Omit for "most of the shape."
❓ Frequently asked
What is the difference between Pick and Omit in TypeScript?
Pick
When should I use Pick vs Omit?
Use Omit when you're excluding specific sensitive or immutable fields from an otherwise complete type. Use Pick when the resulting type is genuinely a small subset — a preview, a list item, a search result with only 2-3 fields.
Can I use both Pick and Omit together?
Yes, they compose. Pick
Does Pick/Omit affect the original type?
No. Pick and Omit are pure type transformations — they produce a new type and leave the original untouched. At runtime, the original object is the same object — Pick/Omit have zero runtime effect. If you need to strip fields at runtime, use destructuring.