Skip to main content

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: Omit subtracts keys from a full type; Pick builds 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? Use Pick
  • No runtime cost: Omit is erased during compilation to JavaScript
  • Works on top-level keys only; nested paths like "address.zip" have no effect

Quick example

typescript
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 changed

Two 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

UtilityWhat it doesBest for
Pick<T, K>Keeps only named keysSmall subset from a large type (2-3 props)
Omit<T, K>Removes named keys, keeps the restMost props needed, exclude a few (secrets, internals)
Decision ruleKeys to exclude < keys to keep? Use Omit. Otherwise Pick

These two produce identical results:

typescript
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 identical

Use 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
// 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 result

No 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

typescript
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

typescript
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

typescript
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

typescript
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'> in forwardRef wrappers (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

typescript
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

typescript
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 fields

Omit combined with Partial covers most CRUD shapes. Define the source type once and derive everything else from it.

Advanced: React component props with forwardRef

typescript
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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?