TypeScript Omit

September 15, 202611 min read
Requirements:
ObjectsGenerics| Unions

Omit<T, K> builds a new object type from T with the keys in K removed. TypeScript Omit is what you reach for when a type you already have is almost right and you need it minus a field or two.

That part is easy, and it is roughly all the handbook tells you. What earns this utility a page of its own is everything it does not do — it will not tell you when you remove a key that does not exist, it behaves badly on unions, and it silently drops things that are not properties. All three are cheap to work around once you know they are there, and all three are quietly wrong in a lot of codebases.

The shape

Omit takes two type parameters: the object type, and the key or keys to strip.

interface BlogUser {
  id: string
  email: string
  passwordHash: string
  createdAt: Date
}
 
type PublicUser = Omit<BlogUser, 'passwordHash'>
// { id: string; email: string; createdAt: Date }
 
const profile: PublicUser = {
  id: 'u_1',
  email: 'ada@example.com',
  createdAt: new Date(),
}

Pass a union of keys to remove several at once. This is the shape you write constantly the moment you have a database model and a "thing the client is allowed to send" version of it.

interface DbArticle {
  id: string
  title: string
  createdAt: Date
  updatedAt: Date
}
 
type NewArticleInput = Omit<DbArticle, 'id' | 'createdAt' | 'updatedAt'>
// { title: string }

The payoff is that DbArticle stays the single source of truth. Add a column and the input type follows along; the alternative is two hand-written interfaces that drift apart over a few months.

Omit is a generic type, and it lives in the same standard-library family as Pick, Partial and Record — the utility types TypeScript ships with.

Omit is Pick plus Exclude

There is no compiler magic here. The whole definition in lib.es5.d.ts is one line, and you can write it yourself:

[object Object]

Read it inside out. keyof T gives every key of T, Exclude drops the ones you named, and Pick rebuilds an object from what is left.

interface Post {
  slug: string
  body: string
  draft: boolean
}
 
type PostKeys = keyof Post
// 'slug' | 'body' | 'draft'
 
type KeptKeys = Exclude<PostKeys, 'draft'>
// 'slug' | 'body'
 
type PublishedPost = Pick<Post, KeptKeys>
// { slug: string; body: string }

Because Pick is a mapped type, Omit inherits its good manners. Optional and readonly modifiers survive the trip:

interface DraftDoc {
  readonly id: string
  title?: string
  internalNotes: string
}
 
type VisibleDoc = Omit<DraftDoc, 'internalNotes'>
// { readonly id: string; title?: string }

That is worth knowing, because the naive assumption is that a utility type flattens everything into plain required properties. It does not — anything expressible as a property modifier comes through intact.

Why TypeScript Omit does not catch typos

Look again at that definition: K extends keyof any. Not K extends keyof T. keyof any is just string | number | symbol, so any string at all is a legal second argument.

interface Account {
  id: string
  ownerEmail: string
  billingPlan: string
}
 
type Trimmed = Omit<Account, 'billingPlann'>
// ✅ compiles happily — and `Trimmed` still has all three keys

Nothing removes anything, and nothing complains. Rename billingPlan to plan during a refactor and every Omit<Account, 'billingPlan'> in the codebase keeps compiling while quietly handing back the full type — including the field you thought you had stripped. For a type whose job is often "hide the sensitive bits", that is a bad failure mode.

This was deliberate: constraining K to keyof T breaks Omit on generic and union types where the key set is not known yet. Reasonable for the standard library, but you usually want the stricter version in your own code:

type StrictOmit<T, K extends keyof T> = Omit<T, K>
 
interface Invoice {
  id: string
  total: number
  currency: string
}
 
type Safe = StrictOmit<Invoice, 'currency'>
// { id: string; total: number }
 
// type Unsafe = StrictOmit<Invoice, 'currancy'>
// ❌ Type '"currancy"' does not satisfy the constraint 'keyof Invoice'

Three lines, and the refactor above becomes a compile error instead of a leak. Drop it in a types/ file and use it everywhere T is a concrete object type.

Omit or Exclude

These two get mixed up constantly, and the names are not helping. The distinction is simple once stated: Omit removes properties from an object type, Exclude removes members from a union.

interface Product {
  sku: string
  price: number
  archived: boolean
}
 
// Omit takes an object type and returns an object type
type Listing = Omit<Product, 'archived'>
// { sku: string; price: number }
 
// Exclude takes a union and returns a union
type ProductStatus = 'draft' | 'live' | 'archived'
type ActiveStatus = Exclude<ProductStatus, 'archived'>
// 'draft' | 'live'

If what you have on the left is a { } with named fields, you want Omit. If it is a list of alternatives joined by |, you want Exclude. And as the definition above shows, Omit is built out of Exclude anyway — it uses it on the key union.

Omit and Pick are inverses

Pick keeps what you name, Omit removes it. Any type you can express with one, you can express with the other:

interface Session {
  token: string
  userId: string
  expiresAt: Date
}
 
type JustToken = Pick<Session, 'token'>
type AlsoJustToken = Omit<Session, 'userId' | 'expiresAt'>

Both produce { token: string }, so the choice is about which one survives the next change to Session. Name the shorter list — but think about what happens when a field is added. A new field lands in whatever the Omit version produces and stays out of the Pick version. For a "public view of a private model", that difference matters: Pick is an allowlist, so a new secret column is excluded by default, while Omit is a denylist that leaks it. Use Pick when forgetting to update the type should fail closed.

Omit on a union throws away too much

This is the one that actually bites. Omit does not distribute over a union — it treats the whole union as a single type, and keyof a union is only the keys common to every member.

type Reminder =
  | { kind: 'email'; to: string; subject: string }
  | { kind: 'sms'; to: string; body: string }
 
type Broken = Omit<Reminder, 'to'>
// { kind: 'email' | 'sms' } — `subject` and `body` are gone too

You asked to remove one key and lost three, along with the discriminated union itself. Worse, the result still compiles everywhere it is used; it is just uselessly wide.

The fix is a conditional type with a naked type parameter, which makes TypeScript apply Omit to each member separately:

type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never
 
type Alert =
  | { kind: 'email'; to: string; subject: string }
  | { kind: 'sms'; to: string; body: string }
 
type Fixed = DistributiveOmit<Alert, 'to'>
// { kind: 'email'; subject: string } | { kind: 'sms'; body: string }

T extends unknown looks like a no-op and is doing all the work — a conditional type over a bare type parameter distributes across unions. Keep this next to StrictOmit; any codebase with discriminated unions needs it eventually.

What Omit quietly drops

Pick maps over properties, so anything that is not a property does not come along. Call signatures are the common casualty:

interface Middleware {
  (request: string): string
  label: string
  enabled: boolean
}
 
type MiddlewareMeta = Omit<Middleware, 'enabled'>
// { label: string } — the call signature is gone
 
// const run: MiddlewareMeta = Object.assign((r: string) => r, { label: 'auth' })
// ❌ the target type has no call signatures

Class types lose their private members the same way, because keyof never saw them:

class Repository {
  private connection = 'db://localhost'
 
  constructor(public table: string) {}
 
  find(id: string) {
    return `${this.connection}/${this.table}/${id}`
  }
}
 
type RepoShape = Omit<Repository, 'find'>
 
const stub: RepoShape = { table: 'users' }
// ✅ structurally fine, but it is not a Repository and never will be

Neither behaviour is a bug — Omit promises you an object type with some keys removed, and that is exactly what you get. Just do not expect it to preserve a class or a callable.

It also hides the result from you

Omit is not eagerly evaluated, so your editor shows you the expression rather than the shape it produces:

interface Customer {
  id: string
  name: string
  taxId: string
}
 
type Billable = Omit<Customer, 'taxId'>
// hovering `Billable` shows: Omit<Customer, "taxId">

That is fine for a one-step type and miserable once you have nested three utilities together — the tooltip grows into an expression you have to evaluate in your head, and so do the error messages. The standard fix is a Prettify helper that maps over the result and forces TypeScript to resolve it:

type Prettify<T> = {
  [K in keyof T]: T[K]
} & {}
 
interface Buyer {
  id: string
  name: string
  taxId: string
}
 
type Billing = Prettify<Omit<Buyer, 'taxId'>>
// hovering `Billing` shows: { id: string; name: string }

The & {} is the part doing the work: intersecting with an empty object gives the compiler a reason to compute the mapped type instead of deferring it. Nothing about the type changes — only what you see. Wrap the types other people have to read.

Omit is also shallow. It removes top-level keys and never looks inside nested objects, which is why stripping an id from a deeply nested model needs a recursive type of your own. The Deep Omit challenge is that type, if you want to build one.

Omit inside your own generics

Returning Omit<T, K> from a generic function is the natural way to type "give me this object without that field". Rest destructuring produces exactly that type, so the obvious implementation checks out:

function stripId<T extends { id: string }>(value: T): Omit<T, 'id'> {
  const { id, ...rest } = value
  return rest
}
 
const withoutId = stripId({ id: 'u_1', email: 'ada@example.com', active: true })
// { email: string; active: boolean }

Where this gets uncomfortable is building the object by hand. While T is still a type parameter, Omit<T, 'id'> is unresolved — the compiler does not know which keys are left, so it will not accept any concrete object you construct:

// function relabel<T extends { id: string; name: string }>(value: T): Omit<T, 'id'> {
//   return { name: value.name }
//   ❌ Type '{ name: string; }' is not assignable to type 'Omit<T, "id">'
// }

The error is correct, even though the code looks right. T may carry extra properties the caller added, and the object literal above does not have them. Either derive the value from the input with a rest element as stripId does, or narrow the return type to the concrete shape you actually build. An as assertion here silences a real warning.

Composing it

Because Omit returns a plain object type, it combines with everything else:

interface ButtonProps {
  label: string
  onClick: () => void
  buttonType: 'button' | 'submit'
  disabled: boolean
}
 
// A link styled as a button: same props, no form behaviour, plus an href
type LinkButtonProps = Omit<ButtonProps, 'buttonType' | 'onClick'> & {
  href: string
}
 
const cta: LinkButtonProps = {
  label: 'Read the docs',
  disabled: false,
  href: '/concepts/typescript-utility-types',
}

Wrapping one variant of a component in terms of another is where most people meet Omit for the first time, and the intersection above is the standard move. It stacks with Partial, Readonly and Record in any order, since all of them are ordinary mapped types operating on the result of the last one.

Wrap up

Omit<T, K> is Pick<T, Exclude<keyof T, K>> and nothing more. Use it to derive a smaller type from a model you already have instead of maintaining two interfaces by hand. Prefer Pick when the type is an allowlist of safe fields and a forgotten update should fail closed. Then write the two helpers this page keeps coming back to — a StrictOmit that constrains K to keyof T, and a DistributiveOmit for unions — because the built-in version will not warn you about either problem.

If you want the mechanics to stick, the Omit challenge has you rebuild it from keyof, Exclude and a mapped type without using Omit itself. It takes about five minutes and it is the fastest way to stop guessing what this type does.

Share this article

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges

Practice with Challenges

Put your typescript omit knowledge to the test with these related challenges.

#3Omit
Medium
#29785Deep Omit
Medium
#4Pick
Easy
#43Exclude
Easy
#7Readonly
Easy

Related Concepts

Concepts that build on or relate to typescript omit.

TypeScript Utility TypesTypeScript RecordMapped TypesTypeScript GenericsInterfaces