TypeScript Pick and Omit

When you already have a well-defined type or interface, you often don’t want to redeclare a smaller or larger version of it by hand. TypeScript’s Pick<T, K> and Omit<T, K> utility types let you derive a new type from an existing one by either keeping only the properties you name (Pick) or dropping the properties you name (Omit). They are two of the most commonly used utility types in real-world TypeScript code, especially for shaping function parameters, API responses, and form data.

Overview / How it works

Pick and Omit are both generic, mapped types built into TypeScript’s standard library (they live in lib.es5.d.ts, so they’re always available with no import). They don’t create runtime values — they only exist at the type level, and like all TypeScript types, they are completely erased once the code is compiled to JavaScript.

Pick<T, K> takes a type T and a union of keys K (where K must extend keyof T), and produces a new object type containing only those keys, with their original types preserved. Omit<T, K> does the opposite: it takes a type T and a union of keys K, and produces a new object type containing every key of T except those in K.

Both are especially useful when a type is the "source of truth" — for example a database row shape or a full domain model — and you need smaller, purpose-built variations of it: a public-facing version without sensitive fields, an input type for creating a record without its generated id, or a preview type with just a few display fields. Instead of maintaining several hand-written interfaces that can drift out of sync, you derive them from one interface, so a change to the source type automatically flows into every derived type.

Because TypeScript uses structural typing, the object type produced by Pick or Omit is compared by shape, not by name. A plain object literal that happens to have the right set of properties will satisfy a Pick– or Omit-derived type even though it was never explicitly annotated with that type name.

Syntax

type Picked = Pick<T, K>;
type Remainder = Omit<T, K>;
  • T — the source object type (an interface, type alias, or class instance type).
  • K in Pick<T, K> — a union of string literal keys that must extend keyof T (the compiler checks this and errors on unknown keys).
  • K in Omit<T, K> — a union of string literal keys constrained only to keyof any (essentially string | number | symbol), not checked against keyof T. This asymmetry is a common source of bugs, covered below.
Utility type Keeps Key constraint Typo caught by compiler?
Pick<T, K> Only the listed keys K extends keyof T Yes
Omit<T, K> Everything except the listed keys K extends keyof any No

Examples

Example 1: Picking a subset of fields

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

type PublicUser = Pick<User, "id" | "name" | "email">;

const user: PublicUser = {
  id: 1,
  name: "Ada Lovelace",
  email: "ada@example.com",
};

console.log(user);

Output:

{ id: 1, name: 'Ada Lovelace', email: 'ada@example.com' }

Here PublicUser is a brand-new object type with exactly three properties, each keeping its original type from User. The password field simply doesn’t exist on PublicUser — trying to access user.password would be a compile error, and trying to assign an object that includes an unexpected password property directly as a PublicUser literal would also fail, because object literals are checked for excess properties.

Example 2: Omitting sensitive fields

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: string;
}

type SafeUser = Omit<User, "password">;

function toSafeUser(user: User): SafeUser {
  const { password, ...rest } = user;
  return rest;
}

const fullUser: User = {
  id: 1,
  name: "Grace Hopper",
  email: "grace@example.com",
  password: "supersecret",
  createdAt: "2024-01-01",
};

console.log(toSafeUser(fullUser));

Output:

{ id: 1, name: 'Grace Hopper', email: 'grace@example.com', createdAt: '2024-01-01' }

SafeUser keeps every property of User except password. Note that Omit only changes the type — it’s still your job to actually strip the property at runtime (here, with object destructuring). If you forgot the destructuring and just returned user cast as SafeUser, the password value would still be present in the actual object at runtime, just invisible to the type checker.

Example 3: Combining Pick and Omit in realistic code

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  inStock: boolean;
}

type ProductPreview = Pick<Product, "id" | "name" | "price">;

type ProductUpdate = Partial<Omit<Product, "id">>;

function updateProduct(product: Product, updates: ProductUpdate): Product {
  return { ...product, ...updates };
}

const product: Product = {
  id: "p1",
  name: "Keyboard",
  price: 49.99,
  description: "Mechanical keyboard",
  inStock: true,
};

const updated = updateProduct(product, { price: 39.99, inStock: false });

const preview: ProductPreview = {
  id: updated.id,
  name: updated.name,
  price: updated.price,
};

console.log(preview);
console.log(updated);

Output:

{ id: 'p1', name: 'Keyboard', price: 39.99 }
{
  id: 'p1',
  name: 'Keyboard',
  price: 39.99,
  description: 'Mechanical keyboard',
  inStock: false
}

This is the pattern you’ll see constantly in real applications: Omit<Product, "id"> removes the server-generated identifier so callers can’t accidentally set it, and wrapping that in Partial<...> makes every remaining field optional so updateProduct accepts a partial patch. ProductPreview, built with Pick, is a lightweight type for a listing page that only needs three fields. Both derived types automatically stay correct if you add or rename fields on Product.

Under the hood

Pick and Omit are themselves just generic mapped types defined using other type-level tools. You can see their real implementation (or write an equivalent) like this:

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type MyOmit<T, K extends keyof any> = MyPick<T, Exclude<keyof T, K>>;

Step by step, when the compiler evaluates Pick<User, "id" | "name">:

  • It checks the constraint: is "id" | "name" assignable to keyof User? If any key isn’t a real property of User, this is a compile error.
  • It iterates the mapped type [P in K]: T[P] for each key P in the union K, looking up that property’s exact type on T.
  • It assembles the results into a fresh anonymous object type — this is why hovering over a Pick-derived type in your editor shows the fully expanded shape, not the words "Pick<…>".

Omit does one extra step first: Exclude<keyof T, K> computes "every key of T that is not in K", and then feeds that resulting key union into the same Pick-style mapping. This is also why Omit‘s second parameter isn’t checked against keyof TExclude is happy to compute the set difference between two unions even if there’s no overlap at all.

Crucially, none of this exists once the code compiles to JavaScript. Run tsc on any of the examples above and open the output .js file — you’ll find no trace of Pick, Omit, or the type annotations at all. Only the runtime logic (the destructuring, the object spreads, the console.log calls) remains. Types are a compile-time-only safety net.

Common Mistakes

Mistake 1: Assuming Omit catches typos the way Pick does

Because Omit‘s key parameter isn’t constrained to keyof T, a misspelled key silently does nothing instead of raising an error:

interface User {
  id: number;
  name: string;
  password: string;
}

// Typo: "passward" instead of "password" -- this compiles with NO error!
type BuggyOmit = Omit<User, "passward">;

const leaked: BuggyOmit = {
  id: 1,
  name: "Eve",
  password: "still-here",
};

console.log(leaked);

Output:

{ id: 1, name: 'Eve', password: 'still-here' }

Since "passward" isn’t a key of User, Exclude<keyof User, "passward"> just returns every key unchanged, so BuggyOmit is identical to User — the password field was never actually removed. This is a genuinely dangerous mistake in security-sensitive code, because it fails silently instead of with a compiler error.

Corrected: spell the key correctly, and double-check by hovering the derived type in your editor to confirm the field you meant to remove is actually gone.

interface User {
  id: number;
  name: string;
  password: string;
}

type FixedOmit = Omit<User, "password">;

const safe: FixedOmit = {
  id: 1,
  name: "Eve",
};

console.log(safe);

Mistake 2: Trying to reach into nested properties

Pick and Omit only operate on the top-level keys of T — you cannot target a property of a nested object using a dotted path:

interface Customer {
  id: number;
  name: string;
  address: {
    street: string;
    city: string;
    zip: string;
  };
}

// Error: Argument of type '"address.city"' is not assignable
// to parameter of type 'keyof any'... (and it wouldn't do what you want anyway)
type WrongNested = Omit<Customer, "address.city">;

Corrected: if you need to reshape a nested object, apply Pick/Omit directly to the nested type and then rebuild the outer type around it:

interface Address {
  street: string;
  city: string;
  zip: string;
}

interface Customer {
  id: number;
  name: string;
  address: Address;
}

type AddressWithoutZip = Omit<Address, "zip">;

type CustomerWithShortAddress = Omit<Customer, "address"> & {
  address: AddressWithoutZip;
};

const customer: CustomerWithShortAddress = {
  id: 1,
  name: "Alan Turing",
  address: { street: "1 Computing Ave", city: "Manchester" },
};

console.log(customer.address.city);

Output:

Manchester

Best Practices

  • Prefer deriving related types with Pick/Omit from one source interface instead of hand-writing several near-duplicate interfaces that can drift out of sync.
  • When using Omit, hover the resulting type in your editor (or check with a quick throwaway variable) to confirm the field you intended to remove is truly gone, since typos aren’t caught.
  • Combine with Partial for "update" or "patch" style inputs (Partial<Omit<T, "id">>), and with Required when you need to force every remaining field to be present.
  • Use Pick for "shrink" scenarios (small preview/summary types) and Omit for "shrink by exception" scenarios (everything except one or two sensitive/generated fields).
  • For deeply nested reshaping, apply Pick/Omit to the nested type directly rather than trying to express a dotted path.
  • Avoid chaining more than two or three utility types in one line (e.g. Partial<Omit<Pick<T, ...>, ...>>) — extract an intermediate named type instead, for readability.

Practice Exercises

  • Given an interface Article { id: string; title: string; body: string; authorId: string; publishedAt: string; }, define an ArticleSummary type using Pick that contains only id, title, and publishedAt, then write a value of that type.
  • Using the same Article interface, define a NewArticleInput type with Omit that removes id and publishedAt (the fields a server would generate), then write a function createArticle(input: NewArticleInput): Article that fabricates the missing fields.
  • Predict, then verify: does Omit<Article, "autorId"> (note the typo) produce a compile error? Write it out and explain in your own words why or why not, referencing the key constraint difference between Pick and Omit.

Summary

  • Pick<T, K> builds a new type containing only the listed keys of T; Omit<T, K> builds a new type containing every key except the listed ones.
  • Pick‘s key parameter is constrained to keyof T, so unknown keys are compile errors; Omit‘s key parameter is not, so typos silently do nothing instead of failing to compile.
  • Both are implemented as mapped types over keyof T/Exclude, and like all TypeScript types, they are fully erased at runtime — they only affect compile-time checking.
  • They operate on top-level keys only; nested objects need their own Pick/Omit applied directly.
  • Combine them with Partial, Required, or intersections (&) to model common patterns like update/patch inputs and previews.