Pick and Omit Without Duplicating an Interface
Derive a public type from User with Omit and a short card type with Pick, then close the gap where Omit accepts a misspelled key. After this, one change to User updates every smaller type.
A User type with a passwordHash field should not be the type that goes to the browser. The quick answer is to copy the interface and delete the field. The copy works on the day you write it and goes stale the first time User changes. Omit and Pick build the smaller type from the original, so there is only one place to edit.
Step 1: the copy that goes stale
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
}
interface PublicUser {
id: number;
name: string;
}
function toPublic(user: User): PublicUser {
return { id: user.id, name: user.name };
}
const publicUser: PublicUser = toPublic({ id: 1, name: "Ana", email: "ana@example.com", passwordHash: "x" });
console.log(publicUser.email);
copy.ts(18,24): error TS2339: Property 'email' does not exist on type 'PublicUser'.
PublicUser was supposed to include the email. Someone forgot to add it when User grew, and the compiler cannot know the two are meant to match. Nothing links them.
Step 2: derive it with Omit
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
}
type PublicUser = Omit<User, "passwordHash">;
function toPublic(user: User): PublicUser {
const { passwordHash, ...rest } = user;
return rest;
}
console.log(toPublic({ id: 1, name: "Ana", email: "ana@example.com", passwordHash: "x" }));
{ id: 1, name: 'Ana', email: 'ana@example.com' }
Omit<User, "passwordHash"> is "everything in User except passwordHash". Add a field to User and PublicUser gets it. Note that the type does not remove anything at run time. The destructuring in toPublic does that.
Step 3: use Pick when the list is short
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
}
type UserCard = Pick<User, "id" | "name">;
const card: UserCard = { id: 1, name: "Ana", email: "ana@example.com" };
pick.ts(10,46): error TS2353: Object literal may only specify known properties, and 'email' does not exist in type 'UserCard'.
Pick keeps the names you list. Use Pick when you want two or three properties and Omit when you want to drop two or three.
Step 4: close the gap in Omit
Omit does not check that the name exists.
interface User {
id: number;
name: string;
passwordHash: string;
}
type PublicUser = Omit<User, "passwordhash">;
const leaked: PublicUser = { id: 1, name: "Ana", passwordHash: "x" };
console.log(leaked);
The key has the wrong capital letter, so nothing is removed, and passwordHash is still allowed in the type. There is no error. For anything where the name matters, write your own stricter version once.
interface User {
id: number;
name: string;
passwordHash: string;
}
type StrictOmit<T, K extends keyof T> = Omit<T, K>;
type PublicUser = StrictOmit<User, "passwordhash">;
strict_omit.ts(9,36): error TS2344: Type '"passwordhash"' does not satisfy the constraint 'keyof User'.
K extends keyof T limits the second argument to real property names. Put StrictOmit in a shared file and use it everywhere instead of Omit.
⚠ Warning: A type that drops passwordHash does not stop the field from being sent. Types are erased. The object you return still has whatever you put in it. Remove the field in code, as toPublic does.
Common mistakes
- Copying an interface "just for the API response".
- Trusting
Omitwith a name you typed by hand. - Thinking
Omitdeletes the property from the data.
Related course: TypeScript Intermediate.
About
Derive a public type from User with Omit and a short card type with Pick, then close the gap where Omit accepts a misspelled key. After this, one change to User updates every smaller type.
- Responsible
- John G
- Last Update
- 10/15/2026
- Reading Time
- 3 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.