Trung bình JavaScript

TypeScript Utility Types: Pick, Omit, and Partial Explained

Stop duplicating interfaces for every slightly-different shape. Pick, Omit, and Partial let you derive new types from one source of truth — here's when to reach for each.

22 Th07, 2026 3 phút 7 Lượt xem 5 Khối code
Sơ đồ
graph TD A["User (source of truth)"] --> B["Partial<User><br/>all fields optional"] A --> C["Pick<User, 'id' | 'name'><br/>only listed fields"] A --> D["Omit<User, 'passwordHash'><br/>everything except listed"] C --> E["Partial<Pick<User,...>><br/>subset, all optional"]

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

graph TD A["User (source of truth)"] --> B["Partial&lt;User&gt;<br/>all fields optional"] A --> C["Pick&lt;User, 'id' | 'name'&gt;<br/>only listed fields"] A --> D["Omit&lt;User, 'passwordHash'&gt;<br/>everything except listed"] C --> E["Partial&lt;Pick&lt;User,...&gt;&gt;<br/>subset, all optional"]

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 forgot id" from "the caller doesn't want to change id." Keep required identifiers (like id) outside the Partial wrapper: { id: string } & Partial<Omit<User, 'id'>>.
  • Reaching for Record<string, unknown> instead of Pick/Omit throws away type safety entirely — utility types keep the connection to the original shape, a loose Record doesn't.

Derive, don't duplicate. If two types describe the same entity, one of them should be built from the other with Pick, Omit, or Partial — not maintained by hand.

Cùng chủ đề JavaScript