skip to content

In TypeScript, why does `Omit<User, "nmae">` compile without complaint when `User` has no `nmae` property, while `Pick<User, "nmae">` is a compile error?

level: middleimportance: must knowfreq 60%

answer

  1. look at the two constraints
  2. keyof T versus keyof any
  3. set subtraction of an absent element
  4. identity, not error
  5. typo survives the rename

basics

~20 s

Their key parameters are constrained differently. Pick declares K extends keyof T, so a typo fails the constraint. Omit declares K extends keyof any, accepting any property name, then subtracts it from T's keys — subtracting a key that is absent simply removes nothing.

solid answer

~50 s

`Pick<T, K extends keyof T>` constrains its key argument to the actual keys of `T`, so `"nmae"` does not satisfy the constraint and you get an error naming the legal keys. `Omit<T, K extends keyof any>` constrains `K` only to `string | number | symbol`, so any name is accepted. Omit is then defined as `Pick<T, Exclude<keyof T, K>>` — it computes `T`'s keys, subtracts yours, and picks the rest. Subtracting a key that was never in the set is a no-op, so `Omit<User, "nmae">` quietly evaluates to `User` unchanged. The looser constraint is deliberate: it lets `Omit` be used on generic and union types where the keys are not statically known. The practical consequence is that a typo or a renamed field turns your Omit into a silent identity — which is exactly how a field you meant to hide leaks back into a response type.

code

typescript · 11 lines
typescript
interface User { id: string; name: string }

// Pick rejects the typo:
// type A = Pick<User, "nmae">;
// Type '"nmae"' does not satisfy the constraint '"id" | "name"'.

// Omit accepts it and quietly returns User unchanged:
type B = Omit<User, "nmae">;

const still: B = { id: "1", name: "Ada" };
console.log(still.name);

go deeper

for a junior

Know that Omit tolerates a key that does not exist and gives you the original type back, while Pick refuses it. Recognising the asymmetry is enough at this level.

for a middle

Explain it from the declarations: K extends keyof T on Pick versus K extends keyof any on Omit, then expand Omit to Pick<T, Exclude<keyof T, K>> and show that subtracting an absent key is a no-op.

for a senior

Point at the real damage — a renamed source field turns an Omit into a silent identity and a secret reappears in a response type. Show the StrictOmit wrapper and say where you would enforce it.

for a principal

Own the trade the library made: usability in generic and union positions bought at the price of typo-detection everywhere else. Be ready to argue whether your codebase should standardise on a stricter alias and how you would migrate to it.

## The two declarations Everything here follows from one line of each definition in the standard library: ```ts type Pick<T, K extends keyof T> = { [P in K]: T[P] }; type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; ``` A *constraint* — the `extends` clause on a type parameter — is a rule the compiler enforces on whatever you pass in. `Pick`'s constraint is `keyof T`: the union of `T`'s own property names. `Omit`'s constraint is `keyof any`, which resolves to `string | number | symbol` — in other words, any value that could legally be a property key at all. So with `interface User { id: string; name: string }`: - `Pick<User, "nmae">` — the compiler checks `"nmae"` against `keyof User`, which is `"id" | "name"`. It does not match, so you get an error along the lines of *Type '"nmae"' does not satisfy the constraint '"id" | "name"'*. - `Omit<User, "nmae">` — `"nmae"` is a string, so it satisfies `keyof any`. No error. ## What Omit then computes Accepting the key is only half the story; the other half is what happens next. `Omit` expands to `Pick<T, Exclude<keyof T, K>>`. Step through it: 1. `keyof User` is `"id" | "name"`. 2. `Exclude<"id" | "name", "nmae">` removes any member assignable to `"nmae"`. Neither is, so the union comes back untouched as `"id" | "name"`. 3. `Pick<User, "id" | "name">` is `User` again. The result is that `Omit<User, "nmae">` *is* `User`. Nothing was removed and nothing was flagged. Set subtraction of an element that is not in the set is simply the original set — the type system is behaving exactly as its definition says, which is why this is not classed as a bug. ## Why the constraint is loose on purpose The obvious question is why `Omit` was not written as `K extends keyof T`. The reason is that `Omit` has to work in positions where the compiler cannot yet prove the relationship. Two common ones: ```ts function strip<T, K extends string>(obj: T, key: K): Omit<T, K> { const { [key]: _drop, ...rest } = obj as Record<string, unknown>; return rest as Omit<T, K>; } ``` Here `T` is an unresolved type parameter, so `keyof T` is unresolved too; a tight constraint would force every caller of a helper like this to thread the relationship through by hand. The same applies when the source type is a union: `keyof (A | B)` is only the keys the members share, so a key that exists on `A` alone would be rejected by a tight constraint even though omitting it is a perfectly sensible request. The designers chose usability in those generic positions over typo-detection in the common one. Whether that was the right trade is a fair thing to have an opinion about in an interview; what matters is knowing the trade exists and which way it went. ## Why it bites in practice The failure mode is silent and directional. Suppose a response type is written as `Omit<UserEntity, "passwordHash">`. Later someone renames the column and the field becomes `passwordDigest`. The `Omit` still compiles — it now subtracts a key that no longer exists — and the response type quietly regains the secret field. No error, no warning, no failing test unless someone wrote one that asserts on the shape. The same thing happens with a plain typo on the day the code is written, and with a field deleted from the source type during a refactor. ## Getting the check back If you want a typo to fail the build, you can constrain the key yourself at the use site or wrap `Omit` in a stricter alias: ```ts type StrictOmit<T, K extends keyof T> = Omit<T, K>; interface User { id: string; name: string; passwordHash: string } type Public = StrictOmit<User, "passwordHash">; // ok // type Oops = StrictOmit<User, "nmae">; // error, as desired ``` The alias adds nothing to the computation; it only re-imposes the constraint `Omit` chose to drop. Many codebases define exactly this once and use it everywhere the source type is concrete. A second, cheaper habit is to prefer `Pick` when you are naming a handful of keys anyway — `Pick` checks them for you, so the typo cannot survive. ## The check to remember If someone asks you to predict `Omit<T, K>` for an unfamiliar `K`, expand it: keys of `T`, minus `K`, picked. That expansion answers this question, and it also explains why `Omit` behaves oddly on union types, where `keyof T` is narrower than you would expect.

  • Why did the standard library not just constrain Omit's key parameter to `keyof T`?
    Because Omit has to work where `keyof T` is not yet resolvable. Inside a generic helper the source type is still a type parameter, and over a union `keyof T` is only the shared keys — so a key valid on one member would be rejected. The loose constraint keeps those uses working, at the cost of never catching a typo.
  • How would you make a typo in an Omit key fail the build?
    Define `type StrictOmit<T, K extends keyof T> = Omit<T, K>` and use it wherever the source type is concrete. The alias computes the same result but re-imposes the constraint, so an unknown key fails. Preferring `Pick` where you are naming only a few keys gets the same protection for free, since Pick already constrains its keys.
  • What does `Omit<User, "id" | "nmae">` evaluate to?
    It removes `id` and ignores `nmae`, giving you `User` without `id`. Omit subtracts the intersection of your key union with the real keys; members that match nothing simply contribute nothing to the subtraction, so a partly-mistyped union still removes the keys that were spelled correctly.

saying these in an interview costs you the question

  • Claiming Omit errors on unknown keys under strict mode
  • Saying the difference is a bug rather than the declared constraint
  • Thinking Omit returns never or an empty type for an unknown key
  • Believing a compiler flag can tighten Omit's key checking
  • Assuming Pick and Omit validate keys identically

context