Difficulty: Advanced
`Pick<T, K>` creates a new type by selecting a subset of properties from `T`. The second type parameter `K` must be a union of keys that exist in `T`. This is useful when you need a view of an object that only includes certain fields - for example, creating a summary type from a detailed entity, or extracting just the fields needed for a specific API call.
`Omit<T, K>` is the complement of `Pick` - it creates a new type by removing specified properties from `T`. If you have a `User` type with 10 properties and you want everything except `password` and `ssn`, `Omit<User, 'password' | 'ssn'>` is much more concise than picking the other 8 fields. Under the hood, `Omit` is defined as `Pick<T, Exclude<keyof T, K>>`.
`Record<K, V>` creates a type with keys of type `K` and values of type `V`. It is the utility type for defining dictionaries and lookup maps. `K` is typically a union of string literals or `string` itself, and `V` is the value type. `Record<string, number>` is equivalent to `{ [key: string]: number }`, but more explicit and composable.
These three utilities are frequently combined in practice. For example, you might use `Pick` to extract relevant fields from a large type, then `Record` to create a mapping from enum values to those picked types. Or use `Omit` to remove sensitive fields before sending data to the client. Mastering these utilities lets you derive precise types from existing ones without duplication.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: string;
}
type UserPreview = Pick<User, "id" | "name" | "email">;
type UserCredentials = Pick<User, "email" | "password">;
function toPreview(user: User): UserPreview {
return { id: user.id, name: user.name, email: user.email };
}
const user: User = {
id: 1, name: "Alice", email: "alice@test.com",
password: "secret", createdAt: "2025-01-01"
};
const preview = toPreview(user);
console.log(`${preview.id}: ${preview.name} (${preview.email})`);
Pick<User, 'id' | 'name' | 'email'> creates a type with only those three properties, useful for API responses that should not expose sensitive fields.
interface Product {
id: string;
name: string;
price: number;
internalCode: string;
supplierNotes: string;
}
type PublicProduct = Omit<Product, "internalCode" | "supplierNotes">;
function sanitize(product: Product): PublicProduct {
const { internalCode, supplierNotes, ...public_ } = product;
return public_;
}
const product: Product = {
id: "p1", name: "Widget", price: 29.99,
internalCode: "WDG-001", supplierNotes: "Fragile"
};
const safe = sanitize(product);
console.log(`${safe.name}: ${safe.price}`);
Omit removes the internal fields, creating a safe type for external consumption. Destructuring with rest handles the runtime removal.
type Role = "admin" | "editor" | "viewer";
interface Permissions {
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
}
const rolePermissions: Record<Role, Permissions> = {
admin: { canRead: true, canWrite: true, canDelete: true },
editor: { canRead: true, canWrite: true, canDelete: false },
viewer: { canRead: true, canWrite: false, canDelete: false }
};
function describeRole(role: Role): string {
const perms = rolePermissions[role];
const abilities = [];
if (perms.canRead) abilities.push("read");
if (perms.canWrite) abilities.push("write");
if (perms.canDelete) abilities.push("delete");
return `${role}: ${abilities.join(", ")}`;
}
console.log(describeRole("admin"));
console.log(describeRole("viewer"));
Record<Role, Permissions> ensures every role has a permissions entry. If a new role is added to the union, TypeScript will error until its permissions are defined.
Pick<T,K>, Omit<T,K>, Record<K,V>, type projection, index signatures