TypeScript Omit
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 keysNothing 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 tooYou 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 signaturesClass 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 beNeither 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.
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