Problem Solved

TypeScript · Pick vs Omit

Home › Courses › TypeScript › Problems › Pick vs Omit
🔷 Problem · 5 min read

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.

omit.ts
// ─── 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.

pick.ts
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.

composition.ts
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 RequestBodyextends 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.

1. Using Pick when Omit is safer

Picking every field except password — fragile. Fix: Omit. New fields included automatically.

2. Using Omit for tiny subsets

Omit when you only want id and name. Fix: Pick.

3. Using Pick/Omit at runtime

Expecting the object to lose fields. Fix: destructure to actually strip. const { password, ...safe } = user.

4. Forgetting Partial for PATCH

Omit requires all other fields. Fix: Partial> for optional updates.

5. Not knowing Pick/Omit exist

Writing { id: string; name: string } by hand instead of Pick. Fix: reuse the base type.

6. Pick with non-existent keys

Pick — compile error. Good! But confusing if you don't read the message. Fix: check the key names.

7. Omit with non-existent keys

Omit — silently does nothing (no error for keys not in T in some versions). Fix: verify the key names match.

8. Using Pick for everything

"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 creates a new type containing ONLY the specified keys — a whitelist. Omit creates a new type containing everything EXCEPT the specified keys — a blacklist. Both produce new types at compile time with zero runtime cost.

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, 'id' | 'name'> first removes password, then picks only id and name. The order matters if the operations affect the same keys — but usually you don't need both at once.

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.

🔗 Related

← All TypeScript problems
Problem solved · TypeScript