skip to content

In TypeScript, given `type A = { id: number; name: string }` and `type B = { id: number; age: number }`, what is `keyof (A | B)`, and what rule produces that answer?

level: middleimportance: should knowfreq 42%

answer

  1. only what every member guarantees
  2. the operator flips
  3. union of types, intersection of keys
  4. shared keys only, variant fields dropped
  5. empty overlap collapses to never

basics

~20 s

keyof (A | B) is "id" — only the keys every member has. keyof distributes over the union and the results are intersected, because a value of a union type is guaranteed to carry just the shared properties. The mirror case: keyof (A & B) is "id" | "name" | "age".

solid answer

~40 s

`keyof (A | B)` is `"id"` — just the keys present in *every* member. The rule is that `keyof` over a union becomes the **intersection** of the members' key unions: `keyof A & keyof B` is `("id" | "name") & ("id" | "age")`, which reduces to `"id"`. That is the sound answer, because a value typed `A | B` might be either one, so the only keys you can safely read are the shared ones. The mirror case flips: `keyof (A & B)` is `keyof A | keyof B`, i.e. `"id" | "name" | "age"`, since an intersection value has all the properties of both. If two union members share nothing, `keyof` of the union is `never` — a common surprise when a helper generic silently produces an empty key set.

go deeper

for a junior

Remember the headline: keyof on a union of object types gives only the keys that all the members share, not everything you can see in the declarations.

for a middle

State the rule as a transformation — keyof (A | B) equals keyof A & keyof B — and work the literal intersection through to show why only the shared key survives.

for a senior

Recognise the symptom in real code: a generic helper that mysteriously yields an empty or tiny key set because it was instantiated with a union, and explain that the type system is being sound rather than unhelpful.

for a principal

Own the design implication: if shared helpers must work across a family of variant shapes, decide whether the contract should be expressed over the common core or over each variant, rather than letting an accidental never key set define the API.

## The result ```ts type A = { id: number; name: string }; type B = { id: number; age: number }; type K = keyof (A | B); // "id" ``` Not `"id" | "name" | "age"`. `keyof` applied to a union yields only the keys that **every** member declares. ## The rule behind it `keyof` distributes across the union and the per-member results are combined with an intersection: ``` keyof (A | B) === keyof A & keyof B ``` Substituting: `keyof A` is `"id" | "name"`, `keyof B` is `"id" | "age"`. The intersection of two unions of literal types keeps only the literals that appear in both, so `("id" | "name") & ("id" | "age")` reduces to `"id"`. Why an intersection rather than a union? Because a union type is a statement of uncertainty. A value typed `A | B` is *one of* those shapes, and the checker does not know which. The only property accesses it can guarantee are those that succeed no matter which member you actually hold. `name` exists on `A` but not on `B`, so it is not a key of the union. Returning `"id" | "name" | "age"` would license `value[k]` for a key that may not exist — unsound. This lines up with what you already know about reading properties from a union value: without narrowing, you can only touch shared members. `keyof` is the type-level statement of that same fact. ## The mirror case: intersections The two operators swap: ```ts type J = keyof (A & B); // "id" | "name" | "age" ``` An intersection value satisfies *both* shapes at once, so every key of either is available, and `keyof (A & B)` is `keyof A | keyof B`. The pairing is worth memorising as one fact rather than two: **`keyof` of a union is the intersection of the keys; `keyof` of an intersection is the union of the keys.** ## The `never` case When union members share no keys, the intersection is empty: ```ts type X = { a: string }; type Y = { b: number }; type Empty = keyof (X | Y); // never ``` `never` is the empty set of possibilities, and it usually shows up as a downstream mystery rather than an error at this line. A helper that does something per key will produce a type with no members; a function whose parameter is typed `keyof (X | Y)` becomes uncallable, because no value inhabits `never`. If a generic helper is behaving as though a type had no properties, check whether the argument was a union of dissimilar shapes. ## Where it bites The usual encounter is a helper written against `keyof T` that works perfectly on a single object type and collapses when someone instantiates it with a union — a discriminated union of message shapes, say, or a state type with several variants. The helper is not wrong; `keyof` is telling the truth about what a union guarantees. Discriminated unions are the interesting sub-case: the discriminant property is by construction present on every member, so it survives the intersection while all the variant-specific fields disappear. If you genuinely want the union of every member's keys, that is a different computation — you have to apply `keyof` to each member separately and union the results, rather than applying it to the union as a whole. Reach for that deliberately, and be aware that the resulting key set is *not* safe for reading properties off a union-typed value; it describes what some member might have, not what any given value does have. ## Erasure, as always None of this is observable at runtime. `keyof (A | B)` is a compile-time computation over declared shapes; the emitted JavaScript contains no trace of it, and it says nothing about which properties a particular object carries when the program runs. It constrains what the checker will let you write, and that is the whole of its effect.

  • What is `keyof (A & B)` for the same two types, and why does it differ?
    It is `"id" | "name" | "age"` — the union of both key sets. A value of an intersection type satisfies both shapes simultaneously, so every property of either one is definitely present and safe to read. Union and intersection swap roles: `keyof` of a union intersects the keys, `keyof` of an intersection unions them.
  • What is `keyof` of a union whose members share no properties at all?
    `never` — the empty intersection. Nothing errors at that line, so it typically surfaces later as a helper that produces a type with no members, or a parameter typed `never` that no argument can satisfy. When a generic behaves as if a type had no keys, a union of dissimilar shapes is the usual cause.
  • For a discriminated union, which keys survive `keyof`?
    The discriminant, plus any other property every variant declares. Since the discriminant exists on all members by construction, it is always in the intersection, while the variant-specific fields drop out. That is consistent with what you can read from an un-narrowed union value: only the common properties are guaranteed to be there.

saying these in an interview costs you the question

  • Says keyof of a union unions all members' keys
  • Cannot explain why the operator flips to intersection
  • Treats a never key set as a compiler bug
  • Assumes narrowing changes what keyof computes
  • Confuses keyof of an intersection with keyof of a union

context