Pick & Omit
Pick<T, K> and Omit<T, K> are complementary utility types that let you create new object types by selecting or excluding specific keys from an existing type. They are among the most frequently used tools in any TypeScript codebase, especially when shaping data at API boundaries.
Pick<T, K>
Pick<T, K> creates a new type by keeping only the properties listed in K from type T. K must be a union of keys that exist on T.
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
role: "admin" | "editor" | "viewer";
}
// Only expose what a profile card needs
type UserCard = Pick<User, "id" | "name">;
/*
{
id: number;
name: string;
}
*/
// Only expose what a list view needs
type UserListItem = Pick<User, "id" | "name" | "role">;
/*
{
id: number;
name: string;
role: "admin" | "editor" | "viewer";
}
*/T, TypeScript raises a compile error — Pick is key-safe.How Pick Is Implemented
Pick is a mapped type that iterates over only the keys listed in K:
// TypeScript's built-in definition
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// The constraint K extends keyof T guarantees that
// every key in K is a valid key on T.Omit<T, K>
Omit<T, K> is the inverse: it creates a new type containing every property from T except the ones listed in K.
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
role: "admin" | "editor" | "viewer";
}
// Remove sensitive fields before sending to the client
type PublicUser = Omit<User, "passwordHash">;
/*
{
id: number;
name: string;
email: string;
createdAt: Date;
role: "admin" | "editor" | "viewer";
}
*/
// Remove fields that should not be in a create request
type CreateUserInput = Omit<User, "id" | "createdAt" | "passwordHash">;
/*
{
name: string;
email: string;
role: "admin" | "editor" | "viewer";
}
*/How Omit Is Implemented
// TypeScript's built-in definition (simplified) type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; // It combines Pick and Exclude: // 1. Exclude<keyof T, K> — remove the unwanted keys from T's key union // 2. Pick<T, ...> — keep only the remaining keys
Omit accepts K extends keyof any (not keyof T), so it does not error if you try to omit a key that does not exist. Pick is stricter on this point.Pick vs Omit — Choosing Between Them
Use Pick when... | Use Omit when... |
|---|---|
The subset is small (2-3 keys) | The subset is large (most keys) |
You want to be explicit about what is included | You want to be explicit about what is excluded |
The source type is large and you only need a few fields | You need everything except one or two sensitive fields |
Creating a narrowly-scoped DTO or view model | Stripping internal / private fields for public APIs |
A practical rule of thumb: if you are keeping fewer than half the keys, use Pick. If you are removing fewer keys than you are keeping, use Omit.
API Response Shaping
The most common real-world use of Pick and Omit is controlling what data crosses API boundaries:
interface Order {
id: string;
userId: string;
items: { productId: string; quantity: number; price: number }[];
total: number;
status: "pending" | "shipped" | "delivered" | "cancelled";
internalNotes: string; // ops-only field
paymentToken: string; // never expose to clients
}
// What the client GET /orders/:id response looks like
type OrderResponse = Omit<Order, "internalNotes" | "paymentToken">;
// What the order list endpoint returns (lighter payload)
type OrderSummary = Pick<Order, "id" | "total" | "status">;
function formatOrderResponse(order: Order): OrderResponse {
const { internalNotes, paymentToken, ...rest } = order;
return rest;
}
function formatOrderSummary(order: Order): OrderSummary {
return { id: order.id, total: order.total, status: order.status };
}Data Transfer Objects (DTOs)
interface Article {
id: number;
title: string;
slug: string;
body: string;
authorId: number;
publishedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
// Create: author provides title, slug, body only
type CreateArticleDTO = Pick<Article, "title" | "slug" | "body">;
// Update: id is required, everything else optional
type UpdateArticleDTO = Pick<Article, "id"> &
Partial<Pick<Article, "title" | "slug" | "body">>;
// Publish action: just needs the id
type PublishArticleDTO = Pick<Article, "id">;
async function createArticle(dto: CreateArticleDTO): Promise<Article> {
// fill in id, authorId, timestamps on the server
return { id: 1, authorId: 42, publishedAt: null, createdAt: new Date(), updatedAt: new Date(), ...dto };
}Form Types
Pick and Omit are great for deriving form types from domain models, avoiding duplication:
interface Product {
id: number;
name: string;
description: string;
price: number;
sku: string;
createdAt: Date;
}
// Form only collects user-editable fields
type ProductFormValues = Omit<Product, "id" | "createdAt">;
// Confirm that no auto-generated fields appear in the form
const initialValues: ProductFormValues = {
name: "",
description: "",
price: 0,
sku: "",
};Pick and Omit vs Intersection Types
You can sometimes achieve similar results with intersection types, but Pick and Omit are usually cleaner and more intentional:
// Intersection approach (verbose, can conflict)
type NameAndEmail = { name: string } & { email: string };
// Pick approach (derives from the source of truth — the User interface)
type NameAndEmailFromUser = Pick<User, "name" | "email">;
// If User changes, Pick<User, ...> stays correct automatically.
// The intersection type would drift out of sync if User types change.
// Intersection is better for ADDING fields:
type UserWithToken = User & { accessToken: string };
// Pick/Omit are better for SUBSETTING or EXCLUDING fields.Combining Pick and Omit
interface Event {
id: string;
title: string;
description: string;
startTime: Date;
endTime: Date;
location: string;
organizerId: string;
capacity: number;
internalCost: number; // private financial field
vendorCode: string; // private ops field
}
// Public view: no internal fields
type PublicEvent = Omit<Event, "internalCost" | "vendorCode">;
// Calendar tile: only needs summary info
type EventTile = Pick<PublicEvent, "id" | "title" | "startTime" | "endTime">;
// Edit form: omit auto-generated id, keep everything else
type EventFormValues = Omit<PublicEvent, "id">;Picking from Interfaces vs Type Aliases
Pick and Omit work identically on both interface and type aliases:
// Works with interface
interface Vehicle {
make: string;
model: string;
year: number;
vin: string;
}
type VehicleCard = Pick<Vehicle, "make" | "model" | "year">;
// Works with type alias
type Point3D = { x: number; y: number; z: number };
type Point2D = Omit<Point3D, "z">;
// Works with generic types
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 = { id: 1, name: "Alice", email: "alice@example.com" };
const preview = pickFields(user, ["id", "name"]);
// { id: 1, name: "Alice" }Pick and Omit too deeply — it makes types hard to read. If you find yourself writing Pick<Omit<Pick<T, ...>, ...>, ...>, consider extracting named intermediate types.Quick Reference
Utility | Syntax | Result |
|---|---|---|
Pick | Pick<User, "id" | "name"> | Only id and name from User |
Omit | Omit<User, "passwordHash"> | User without passwordHash |
Combined | Pick<Omit<User, "secret">, "id" | "name"> | id and name, secret excluded |
With Partial | Partial<Pick<User, "name" | "email">> | Both fields optional |
With Required | Required<Omit<UserForm, "id">> | All fields except id are required |
Pick is your "whitelist" tool for selecting exactly what you need. Omit is your "blacklist" tool for stripping what you do not want. Together they keep your API surface clean and your types derived from a single source of truth.