Let’s talk about the two utility types you’ll use when you want to create a new type by cherry-picking or blacklisting properties from an existing one: Pick<T, K> and Omit<T, K>. They are the scissors and tape of your type system, allowing you to surgically extract the parts of an interface you need or decisively remove the ones you don’t.

Think of Pick as a whitelist. You hand it a type T and a union of property keys K (which must exist on T, or TypeScript will rightly complain), and it gives you a new type containing only those properties.

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

// Let's say we need a type for a public user profile.
// We absolutely do NOT want the passwordHash flying around.
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;

// This is now equivalent to:
// {
//   id: number;
//   name: string;
//   email: string;
// }

const displayProfile = (user: UserProfile) => {
  console.log(`Name: ${user.name}, Email: ${user.email}`);
  // user.passwordHash; // Error: Property 'passwordHash' does not exist on type 'UserProfile'.
};

Omit is its cynical counterpart, the blacklist. You give it a type T and a union of keys K to exclude, and it gives you a type containing everything from T except those properties. It’s the “give me everything but that” operation.

// The same UserProfile type, built by excluding the sensitive stuff.
type UserProfileAlternative = Omit<User, 'passwordHash' | 'createdAt'>;

// Also equivalent to:
// {
//   id: number;
//   name: string;
//   email: string;
// }

Why You’d Use One Over The Other

This isn’t just a coin toss. Pick is declarative and precise. You’re explicitly stating what you want. This is fantastic when the type you’re picking from is large, and you only need a few properties. The resulting type is crystal clear because its definition is a literal list of its constituents.

Omit is often more pragmatic and defensive. You use it when you know what you don’t want, especially when the list of things to exclude is shorter than the list of things to keep. It’s incredibly useful for creating “safe” subtypes by removing dangerous or private fields, exactly as we did with the passwordHash. The beauty of Omit is that it automatically includes any new properties added to T in the future, as long as they aren’t in the exclude list. This can be a feature or a pitfall, depending on your intent.

The Devil’s in the Details: Unions and Literals

Here’s where the “cleverness” of these utilities can bite you. The keys K you provide must be string literals or unions of string literals (keyof T is your best friend here). But what if the original type has optional or readonly properties?

Good news: Pick and Omit preserve the modifiers (readonly, ?) of the original properties. If you pick an optional property, the new property is optional. If you pick a readonly property, it stays readonly.

interface Config {
  readonly apiKey: string;
  endpoint: string;
  retries?: number;
}

type ClientConfig = Pick<Config, 'apiKey' | 'retries'>;
// This is equivalent to:
// {
//   readonly apiKey: string;
//   retries?: number;
// }

The One Major Gotcha: Excess Properties and Object Literals

This is the most common tripwire. Let’s say you have a function that accepts your safe UserProfile type.

function updateProfile(profile: UserProfile) {
  // send update to API
}

You might try to call it with an object that has more than the required properties, like the full User object you just retrieved from the database.

declare const currentUser: User;

updateProfile(currentUser); // This is FINE.
updateProfile({ id: 1, name: 'Alice', email: 'a@example.com', createdAt: new Date() }); // This is an ERROR.

Wait, what? The first call works because TypeScript’s type system is structural, not nominal. currentUser is a User, which has at least the properties of UserProfile, so it’s assignable. The second call fails because when you use an object literal, TypeScript performs “excess property checking” as a safety measure. It assumes if you’re explicitly adding a property that doesn’t exist on the target type (createdAt), you’ve probably made a mistake.

The lesson: Pick and Omit define the minimum shape an object needs to have, not an exact shape. This is almost always what you want, but you need to be aware of this quirk when passing object literals directly. The fix is usually to assign the object to a variable first, which relaxes the excess property check.

In the end, Pick and Omit are less about magic and more about providing a structured, type-safe way to ask for exactly what you need. They are fundamental tools for composing and decomposing types without repeating yourself, which is, after all, the entire point.