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?
answer
- only what every member guarantees
- the operator flips
- union of types, intersection of keys
- shared keys only, variant fields dropped
- empty overlap collapses to never
basics
~20 skeyof (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
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.
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.
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.
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