Utility Types Part 3: Pick and Omit


The utility types covered so far — Partial, Required, Readonly, and Record — each transform all properties of a type uniformly. Pick and Omit work differently: they restructure a type by selecting which properties to include or exclude by name. The result is a new type that shares structure with the source type but contains only the properties you specify. This is the primary mechanism for deriving focused, minimal types from larger base types without repeating property definitions.
What Pick Does
Pick<T, K> takes a type T and a key or union of keys K, and returns a new type containing only the properties of T whose names are in K.
type User = {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
role: "admin" | "user";
};
type UserPreview = Pick<User, "id" | "name">;
// {
// id: number;
// name: string;
// }
type UserProfile = Pick<User, "id" | "name" | "email" | "role">;
// {
// id: number;
// name: string;
// email: string;
// role: "admin" | "user";
// }Pick preserves the exact types of each property — id is still number, role is still "admin" | "user". Only the set of included properties changes. Properties not named in K simply do not appear in the result.
The key constraint is enforced: if K includes a property name that does not exist in T, TypeScript produces an error. Pick<User, "nonexistent"> is a type error.
How Pick Is Implemented
Pick<T, K> is defined as:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};The constraint K extends keyof T ensures that every key in K is an actual property of T. The mapped type then iterates over K — not over keyof T — and uses indexed access T[P] to retrieve the original property type. Properties of T not in K are simply not iterated and therefore not included in the result.
Pick Preserves Optional and Readonly Modifiers
Pick preserves the modifiers of the original properties. If a property in T is optional (?) or readonly (readonly), the picked version of that property retains those modifiers. Pick<T, K> does not strip optionality or add it — it is a structural subset, not a transformation. To combine picking with modifier changes, compose Pick with Partial or Readonly: Partial<Pick<T, K>> produces a type with the named properties all made optional.
What Omit Does
Omit<T, K> is the complement of Pick. It takes a type T and a key or union of keys K, and returns a new type containing every property of T except those named in K.
type User = {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
role: "admin" | "user";
};
type PublicUser = Omit<User, "passwordHash">;
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// role: "admin" | "user";
// }
type NewUser = Omit<User, "id" | "createdAt">;
// {
// name: string;
// email: string;
// passwordHash: string;
// role: "admin" | "user";
// }Omit is useful when you have a large type and only need to remove a few fields. It is the natural inverse of Pick — use Pick when you know which properties to include; use Omit when you know which properties to exclude.
Omit Does Not Enforce That Excluded Keys Exist
Unlike Pick, which constrains K extends keyof T and errors on unknown keys, Omit's key parameter accepts any string | number | symbol. In TypeScript's standard library definition, Omit<T, K extends string | number | symbol> does not require K to be a key of T. Omitting a property name that does not exist in T silently produces T unchanged — no error. This can mask typos in property names. Some teams address this by using a stricter custom StrictOmit that constrains K extends keyof T.
How Omit Is Implemented
Omit<T, K> is defined as:
type Omit<T, K extends string | number | symbol> = Pick<T, Exclude<keyof T, K>>;Omit is implemented in terms of Pick and Exclude. Exclude<keyof T, K> produces the union of all keys in T that are not in K. Pick<T, ...> then selects those remaining keys. The full implementation depends on Exclude, which is covered in the next post — for now, understanding that Omit<T, K> = Pick<T, keys-of-T-not-in-K> is sufficient.
Practical Patterns for Pick
View models and projections. When rendering UI components, a component often needs only a subset of a data model's properties. Pick creates a focused type that documents exactly what the component requires:
type Product = {
id: number;
name: string;
description: string;
price: number;
stockCount: number;
supplierId: number;
createdAt: Date;
};
type ProductCard = Pick<Product, "id" | "name" | "price">;
function renderProductCard(product: ProductCard): string {
return `${product.name} — $${product.price}`;
}The ProductCard type communicates the component's contract precisely: it accepts any object with those three properties, regardless of whether the full Product type or some other type with the same fields is passed.
API response shapes. Different API endpoints return different subsets of a model. Pick names those subsets explicitly:
type UserListItem = Pick<User, "id" | "name" | "role">;
type UserDetailView = Pick<
User,
"id" | "name" | "email" | "role" | "createdAt"
>;Type-safe property selection. Generic functions that accept an array of keys and return a projection can use Pick to type the result:
function pickFields<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
for (const key of keys) {
result[key] = obj[key];
}
return result;
}
const user: User = {
id: 1,
name: "Alice",
email: "[email protected]",
passwordHash: "...",
createdAt: new Date(),
role: "user",
};
const preview = pickFields(user, ["id", "name"]);
// preview: Pick<User, "id" | "name"> = { id: number; name: string }Practical Patterns for Omit
Excluding sensitive fields. The most common use of Omit is removing properties that should not be exposed — passwords, internal identifiers, audit fields:
type SafeUser = Omit<User, "passwordHash">;
function getUserProfile(id: number): Promise<SafeUser> {
// Guaranteed not to include passwordHash in the return type
}Create DTOs. When inserting a new record, the database generates certain fields (id, createdAt). The input type for creation omits those:
type CreateUserInput = Omit<User, "id" | "createdAt">;
// {
// name: string;
// email: string;
// passwordHash: string;
// role: "admin" | "user";
// }
function createUser(input: CreateUserInput): Promise<User> {
// ...
}Update DTOs. Combined with Partial from the previous post, Omit produces the standard update payload type:
type UpdateUserInput = Partial<Omit<User, "id" | "createdAt" | "passwordHash">>;
// {
// name?: string;
// email?: string;
// role?: "admin" | "user";
// }Omit for Derived Types That Stay in Sync
When you define a CreateInput or UpdateInput type using Omit<BaseType, ...>, it automatically stays synchronised with the base type. If you add a field to User, CreateUserInput automatically includes it (unless you explicitly omit it). This is the key advantage over writing each derived type manually — changes to the source type propagate automatically rather than requiring parallel updates across multiple type definitions.
Combining Pick and Omit
Pick and Omit are often combined with other utility types or with each other to express complex transformations in a single, readable expression.
Combining Omit with Partial is covered above. Combining Pick with Readonly produces an immutable projection:
type ReadonlyUserPreview = Readonly<Pick<User, "id" | "name">>;
// { readonly id: number; readonly name: string }Composing multiple Omit calls is sometimes more readable as a single Pick:
// These are equivalent when User has exactly these six properties:
type PublicUser1 = Omit<User, "passwordHash" | "createdAt">;
type PublicUser2 = Pick<User, "id" | "name" | "email" | "role">;Choose Pick when the list of included properties is shorter; choose Omit when the list of excluded properties is shorter. For large types where only one or two fields need to be removed, Omit is almost always the right choice.
Pick and Omit for Interface Segregation
The interface segregation principle states that no code should depend on methods or properties it does not use. Pick implements this principle at the type level — components receive exactly the properties they need, not the full model. This makes dependencies explicit, reduces unintended coupling, and makes types easier to test and reuse. A function typed with Pick<User, "id" | "name"> works with any object that has those two properties, not only with full User objects.
What Is Next
Pick and Omit complete the structural subset utility types. The next post covers Exclude and Extract — two utility types that operate on union types rather than object types. Where Pick and Omit select properties from an object type, Exclude and Extract select members from a union type. Understanding both pairs gives you precise control over both object shapes and union compositions.
Key Takeaways
Pick<T, K>creates a type with only the named properties ofT; the constraintK extends keyof Tensures all named keys existOmit<T, K>creates a type with all properties ofTexcept the named ones; its key parameter is not constrained to actual keys ofT, so typos produce no error- Both utility types preserve the optionality and readonly modifiers of the original properties — they do not transform modifiers, only property membership
Pickis implemented as a mapped type overK;Omitis implemented asPick<T, Exclude<keyof T, K>>- Use
Pickwhen specifying the included properties is shorter; useOmitwhen specifying the excluded properties is shorter - Compose
OmitwithPartialfor update DTOs, withReadonlyfor immutable projections, and with other utility types to build precise derived types that stay synchronised with their source