Utility type omit in TypeScript
Omit<T, K> - a utility type that takes type T and returns the same type with every key listed in K removed.
Theory
TL;DR
- Think of a form with certain fields blacked out: everything else stays intact, only the listed fields disappear
- Main difference from
Pick:Omitsubtracts keys from a full type;Pickbuilds a new type by selecting only what you name - Decision rule: keys to exclude fewer than keys to keep? Use
Omit. Need only a small subset? UsePick - No runtime cost:
Omitis erased during compilation to JavaScript - Works on top-level keys only; nested paths like
"address.zip"have no effect
Quick example
interface User {
id: string;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, "password">;
// Result: { id: string; name: string; email: string }
// password is gone, nothing else changedTwo lines of type code, one removed field. That is the whole idea.
Key difference
Pick<T, K> builds a type from scratch by selecting only the named keys. Omit<T, K> starts from the full type and removes the listed keys, keeping everything else. If a type has 10 properties and you need 9 of them, Omit saves you from listing all 9 in Pick.
When to use
- API response without sensitive fields:
Omit<User, "password" | "internalId"> - React component props without internal callbacks:
Omit<ComponentProps, "onInternalClick"> - Database entity converted to DTO:
Omit<Entity, "createdBy" | "updatedAt"> - Form input type for record creation:
Omit<Product, "id" | "createdAt">
Omit vs Pick
| Utility | What it does | Best for |
|---|---|---|
Pick<T, K> | Keeps only named keys | Small subset from a large type (2-3 props) |
Omit<T, K> | Removes named keys, keeps the rest | Most props needed, exclude a few (secrets, internals) |
| Decision rule | Keys to exclude < keys to keep? Use Omit. Otherwise Pick |
These two produce identical results:
interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
}
type A = Pick<User, "id" | "name" | "email">;
type B = Omit<User, "password" | "createdAt">;
// A and B are structurally identicalUse whichever makes the intent clearer at the call site.
How the compiler handles Omit
TypeScript expands Omit<T, K> using Pick and Exclude internally:
// TypeScript's built-in definition:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// Step by step for Omit<User, "password">:
// 1. keyof User = "id" | "name" | "email" | "password" | "createdAt"
// 2. Exclude<keyof User, "password"> = "id" | "name" | "email" | "createdAt"
// 3. Pick<User, "id" | "name" | "email" | "createdAt"> = the resultNo runtime cost. The whole mechanism disappears after compilation to JavaScript. One more detail: Omit preserves index signatures. If your type has [key: string]: unknown, that stays in the result even after specific keys are removed.
Common mistakes
1. Omitting a key that does not exist
interface User {
id: string;
name: string;
}
type Wrong = Omit<User, "nonExistent">;
// Compiles fine. No error. No effect.
// TypeScript ignores keys in K that do not exist in T.No crash, no warning. The type stays unchanged and you spend time wondering why nothing happened.
2. Expecting Omit to remove nested keys
type UserWithAddress = {
name: string;
address: { street: string; zip: string };
};
// This has no effect:
type Wrong = Omit<UserWithAddress, "address.zip">;
// Result: { name: string; address: { street: string; zip: string } }Omit is shallow. It only removes top-level keys. To drop a nested field, reconstruct the nested type manually or write a recursive utility type.
3. Using Omit on union types without distribution
type Union = string | { id: string; secret: string };
type Attempt = Omit<Union, "secret">;
// Result is not what you expect.
// Omit does not distribute over union members automatically.For unions, apply Omit to each member separately.
4. Re-adding an omitted key via extends
type PublicUser = Omit<User, "password">;
// This compiles:
interface AdminUser extends PublicUser {
password: string; // password is back
}Structural typing allows this. If the goal is to prevent password from appearing anywhere, this approach will not hold. Use an intersection type or a separate base instead.
5. Confusing Omit with Exclude
Exclude<keyof T, K> gives you a union of remaining key names as strings. Omit<T, K> gives you a new object type. Different outputs, different use cases.
Real-world usage
- React:
Omit<ComponentProps<'input'>, 'ref'>inforwardRefwrappers (pattern used in React 18 typings) - Zod:
Omit<z.infer<typeof userSchema>, 'password'>for public schema shapes - Prisma:
Omit<Prisma.UserGetPayload<{}>, 'passwordHash'>in API response types - tRPC: procedure inputs via
Omit<FullInput, 'internalToken'> - Express: narrowing request body types in middleware
In my experience, Omit shows up most at API boundaries where you strip sensitive or server-only fields before the data reaches the client. That single pattern covers roughly 70% of real usages across codebases.
Follow-up questions
Q: What happens if K contains a key that does not exist in T?
A: TypeScript compiles without error and ignores the unknown key. The resulting type is identical to T.
Q: How does Omit handle index signatures like [key: string]: unknown?
A: It preserves them. Omit<{ [k: string]: any; foo: string }, 'foo'> keeps the index signature and removes only foo.
Q: What is the difference between Omit<T, K> and Exclude<keyof T, K>?
A: Exclude returns a union of key names. Omit returns a full object type with those keys removed. One is a string union, the other is a usable object type.
Q: How would you implement Omit from scratch?
A: type MyOmit<T, K extends keyof any> = { [P in keyof T as P extends K ? never : P]: T[P] }. The as clause in the mapped type filters out matching keys by remapping them to never.
Q: How does Omit behave with intersection types like A & B?
A: It distributes: Omit<A & B, K> roughly equals Omit<A, K> & Omit<B, K>. The exact result depends on which type in the intersection actually holds the key.
Examples
Basic: removing a sensitive field
interface User {
id: string;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, "password">;
// { id: string; name: string; email: string }
function getPublicUser(user: User): PublicUser {
const { password, ...rest } = user;
return rest; // TypeScript knows this matches PublicUser
}The spread destructuring pattern pairs naturally with Omit: the type describes the expected shape, the runtime code strips the actual value.
Intermediate: DTO types for a CRUD API
interface Product {
id: string;
name: string;
price: number;
description: string;
createdAt: Date;
updatedAt: Date;
}
// POST /products - no id or timestamps (server sets those)
type CreateProductDTO = Omit<Product, "id" | "createdAt" | "updatedAt">;
// PATCH /products/:id - id comes from the URL, all fields optional
type UpdateProductDTO = Partial<Omit<Product, "id">>;
// These types prevent accidental sends of server-managed fieldsOmit combined with Partial covers most CRUD shapes. Define the source type once and derive everything else from it.
Advanced: React component props with forwardRef
import { forwardRef, InputHTMLAttributes } from "react";
interface CustomInputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, "ref"> {
label: string;
error?: string;
}
const CustomInput = forwardRef<HTMLInputElement, CustomInputProps>(
({ label, error, ...props }, ref) => (
<div>
<label>{label}</label>
<input ref={ref} {...props} />
{error && <span>{error}</span>}
</div>
)
);Omit<InputHTMLAttributes<HTMLInputElement>, 'ref'> strips the built-in ref prop so forwardRef can manage it directly. This is the exact pattern used in React 18 typings.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.