skip to content

In TypeScript, what do `string | never` and `string & never` each reduce to, and why does `{ a: string } & { a: number }` give the `a` property the type `never`?

level: middleimportance: should knowfreq 40%

answer

  1. never is the empty set
  2. identity for one operator, absorbing for the other
  3. union drops it, intersection swallows
  4. string & number has no members
  5. object intersections combine per key

basics

~20 s

string | never reduces to string and string & never reduces to never. Because never is the empty set of values, it disappears from a union and swallows an intersection — and intersecting two incompatible property types leaves that property with no possible value.

solid answer

~50 s

Read the types as sets and both results follow. A union is a set union, so adding the empty set changes nothing: `string | never` is `string`. An intersection is a set intersection, so intersecting with the empty set leaves nothing: `string & never` is `never`. The same arithmetic runs per property when you intersect object types — `{ a: string } & { a: number }` intersects the two `a` types, and since no value is both a string and a number, `a` becomes `never`. That is why an accidental `never` is such a quiet bug: it vanishes from unions without a trace, and in an intersection it can leave you with an object you can read but never construct. When a computed type turns out unusable, checking whether some part of it silently collapsed to `never` is usually the first thing to do.

code

typescript · 13 lines
typescript
type A = string | never;  // string — never adds nothing to a union
type B = string & never;  // never — never empties an intersection

declare const a: A;
const asString: string = a;

// object types intersect property by property
type Both = { a: string } & { a: number };
declare const both: Both;
const prop: never = both.a; // a is string & number => never

// the type exists and can be read, but never constructed
// const oops: Both = { a: "x" }; // Error: 'string' is not assignable to 'never'

go deeper

for a junior

Remember the two reductions: string | never is just string, and string & never is never. Reading the types as sets — never being the empty set — is enough to reproduce both.

for a middle

Explain why the rules hold and apply them to object types, showing that an intersection combines per property so a conflicting key becomes never while the object type itself survives.

for a senior

Show how a silent collapse to never propagates: a generic that computes never gives call sites 'not assignable to never' errors far from the cause. Describe how you localise it by inspecting intermediate aliases.

for a principal

Own the design lesson: shared types assembled by intersecting sources from several teams can become unconstructible without anyone noticing. Decide where the codebase composes with unions and discriminants instead, and how CI catches an unusable public type.

## Types as sets The cleanest way to predict what TypeScript does with `never` is to read each type as a set of values, `|` as set union and `&` as set intersection. `never` is the empty set. From there the rules are arithmetic: - **Union**: `T | never` = `T`. Adding nothing to a set changes nothing, so `never` is the *identity* element for `|` and simply disappears. - **Intersection**: `T & never` = `never`. Intersecting with nothing leaves nothing, so `never` is the *absorbing* element for `&`. ```ts type A = string | never; // string type B = string & never; // never ``` Because the reduction happens eagerly, you rarely *see* a `never` in a union in editor tooltips — it has already been removed by the time the type is displayed. ## Intersections that collapse on their own You do not have to write `never` to get one. An intersection of two types with no common members reduces to `never` by itself: ```ts type Impossible = string & number; // never — no value is both ``` This is the same computation. `string` and `number` are disjoint sets, so their intersection is empty, and the empty type is spelled `never`. ## Property-wise intersection of object types Object types intersect **per property**. `{ a: string } & { a: number }` keeps the key `a` and gives it the intersection of the two declared types: ```ts type Both = { a: string } & { a: number }; declare const both: Both; const value: never = both.a; // a is string & number, i.e. never ``` Note what did *not* happen: the whole object type did not become `never`. `Both` is still an object type with one property — you can declare a variable of it, and you can read `both.a`. What you cannot do is *construct* one, because you would need a value for `a` and none exists. This is a classic trap when merging configuration or prop types from two sources: the type compiles, code that reads it compiles, and only the code that has to produce a value fails, often far away and with a confusing message. ```ts // const oops: Both = { a: "x" }; // Error: 'string' is not assignable to 'never' ``` If you actually wanted "either shape", the operator was `|`, not `&`. ## Why the union rule matters: filtering The disappearing-from-unions behaviour is the mechanism behind type-level filtering. Conditional types distribute over a union member by member, and any member that maps to `never` is dropped from the result, because `never` contributes nothing to a union. That is how the built-in `Exclude` produces a smaller union, and it is why a filter that rejects *every* member yields `never` rather than an empty union — TypeScript has no separate spelling for "union of nothing"; `never` **is** that spelling. The practical consequence: a generic type that quietly computes `never` gives you a type nothing can satisfy. Downstream, callers get errors like "not assignable to type 'never'" at a call site far from the definition. When debugging a generic, hover the intermediate aliases and look for the point where a type became `never`. ## Related shapes that surprise people - `never[]` is a perfectly real array type — the array itself exists and can be empty; it just has no valid element, so you can never `push` into it. It commonly appears when an empty array literal has nothing to infer from. - `keyof` of an object type with no keys is `never`, since there are no key names to name. - `Promise<never>` is a promise that can never fulfil with a value, which is the honest type for a promise that only ever rejects. - A function parameter typed `never` can never be called successfully — nothing is assignable to it. That property is what exhaustiveness helpers exploit. ## Rules of thumb 1. Read `|` and `&` as set operations and `never` as the empty set; every reduction follows. 2. `never` in a union is invisible; `never` in an intersection is fatal to that position. 3. An object intersection collapses **per property**, not as a whole — the type stays constructible-looking but is not constructible. 4. When a generic or utility type behaves as if it accepts nothing, check each intermediate step for a silent collapse to `never`.

  • If `never` disappears from unions, how does TypeScript express "a union with no members"?
    `never` itself is that spelling. There is no separate empty-union type: filtering every member out of a union leaves `never`. That is consistent with the set reading — a union of nothing is the empty set — and it is why a conditional type that rejects all inputs resolves to `never` rather than to some empty-list type.
  • Is `{ a: string } & { a: number }` the same as `never`?
    No. The intersection is still an object type with one property; only that property's type is `never`. You can declare a variable of it and read `.a`, but you cannot construct a value, because no value satisfies `a`. Whole-type collapse to `never` happens for disjoint primitives like `string & number`, not for object types with a conflicting key.
  • Where does `never[]` come from, and can you use it?
    It appears when an array type has no valid element type — for example an empty array literal with nothing to infer from, or an array of a filtered-away union. You can read its `length`, iterate it (it is always empty), and assign `[]` to it, but any `push` fails because nothing is assignable to `never`. Seeing it usually means an annotation or an inference source is missing.

saying these in an interview costs you the question

  • Thinks never in a union makes the whole union never
  • Expects string & number to be an error rather than never
  • Assumes an object intersection with a conflicting key collapses entirely
  • Confuses union and intersection when combining object types
  • Believes never[] is the same as any[] or an error

context