skip to content

In TypeScript, what does an optional property declared with type `never` — as in `{ value: string; defaultValue?: never }` — actually accomplish, and where does the pattern break down?

level: seniorimportance: nice to knowfreq 25%

answer

  1. forbidding a key, not typing it
  2. optional quietly adds undefined back
  3. one branch bans what the other requires
  4. the error message mentions undefined
  5. exactOptionalPropertyTypes tightens it

basics

~20 s

An optional never property forbids a key: it can only be absent or explicitly undefined. Paired across the branches of a union, it makes two properties mutually exclusive, so an object supplying both matches neither branch.

solid answer

~50 s

`defaultValue?: never` says the property may not carry a value. The optional marker adds `undefined` back, so the permitted values are "absent" or "explicitly `undefined`" — nothing else. The pattern earns its keep in a union where each branch bans the other branch's key: `{ value: string; defaultValue?: never } | { defaultValue: string; value?: never }` accepts either property alone and rejects an object with both, since it fits neither member. The limits are real, though. The error message is confusing — it reports that `string` is not assignable to `undefined`, not a word about exclusivity. The technique scales badly: n mutually exclusive options means n hand-written branches, each listing every other key. And it is erased, so nothing stops untyped callers. When the alternatives carry a meaningful mode, a discriminated union with a literal tag narrows better and reads better.

code

typescript · 12 lines
typescript
type Input =
  | { value: string; defaultValue?: never }
  | { defaultValue: string; value?: never };

const controlled: Input = { value: "x" };
const uncontrolled: Input = { defaultValue: "y" };

// @ts-expect-error matches neither branch: each one bans the other's key
const both: Input = { value: "x", defaultValue: "y" };

// still allowed: optionality reintroduces undefined
const explicitUndefined: Input = { value: "x", defaultValue: undefined };

go deeper

for a junior

Know that a property typed never cannot hold a value, and that marking it optional means the key may only be omitted or set to undefined.

for a middle

Explain how pairing the bans across union branches makes two keys mutually exclusive, and why never | undefined collapses to undefined so an explicit undefined still passes.

for a senior

Weigh it against a tagged union: judge the error-message cost, the maintenance burden as options multiply, and the fact that nothing is enforced at runtime for data crossing a boundary.

for a principal

Own the API-shape decision: whether the codebase encodes exclusivity structurally or with discriminants, how that choice reads in errors for consuming teams, and where runtime validation must back it up regardless.

## What the annotation means Write a property as `never` and you have declared a slot that no value can fill, since `never` has no members. Make it **optional** and the optional marker unions `undefined` into the declared type: `never | undefined` reduces to `undefined`. So the permitted states of `defaultValue?: never` are exactly two: the key is absent, or it is present with the value `undefined`. ```ts type OnlyValue = { value: string; defaultValue?: never }; const a: OnlyValue = { value: "x" }; // ok, key absent const b: OnlyValue = { value: "x", defaultValue: undefined }; // ok // const c: OnlyValue = { value: "x", defaultValue: "y" }; // Error ``` On its own that is not very interesting — an optional key you must not set. Its value comes from pairing. ## The exclusive-or pattern The reason anyone writes `?: never` is to make two options mutually exclusive inside a union, where each branch explicitly forbids the other's key: ```ts type Input = | { value: string; defaultValue?: never } | { defaultValue: string; value?: never }; const controlled: Input = { value: "x" }; // ok const uncontrolled: Input = { defaultValue: "y" }; // ok // const both: Input = { value: "x", defaultValue: "y" }; // Error ``` Without the `?: never` members, `{ value: "x", defaultValue: "y" }` would be perfectly assignable to the first branch — an object with extra compatible properties normally satisfies a type. Adding the ban is what closes that door: the object fails branch one because `defaultValue: string` does not fit `undefined`, and fails branch two because it has no `defaultValue: string`. Matching neither member, it is rejected. Importantly this is ordinary assignability, not the freshness check on object literals. A pre-built variable of type `{ value: string; defaultValue: string }` is rejected too, so the guarantee survives being passed through a helper. ## Where it breaks down **The error message does not say what you mean.** For the both-keys case the compiler reports something like *Type 'string' is not assignable to type 'undefined'*. Nothing in that mentions exclusivity, and the reader has to reverse-engineer the intent from the type. On a public API this costs more than it looks — every consumer who trips over it pays the decoding cost. **Explicit `undefined` still passes.** `{ value: "x", defaultValue: undefined }` is accepted, because optionality reintroduced `undefined`. If a caller spreads a partially-filled object, the forbidden key can be present-but-undefined and the check says nothing. Enabling `exactOptionalPropertyTypes` tightens this by separating "absent" from "present and undefined". **It scales quadratically.** Three mutually exclusive options require three branches, each banning the other two keys; adding a fourth option means touching every existing branch. Nothing enforces that you kept the bans in sync, and a forgotten one silently reopens a combination. **No narrowing story.** The pattern rejects bad inputs but gives consumers nothing convenient to switch on. To find out which branch you have, you test key presence (`"value" in x`) rather than reading a tag, and the intent — "this is the controlled mode" — is never written down. **It is erased.** The whole construction is compile-time only. JavaScript callers, `JSON.parse` results, and anything that arrived through an assertion can carry both keys at runtime, and your code must still cope. ## When to reach for it, and when not to Use it when the two options are genuinely just "one of these two fields", there are exactly two or three of them, and adding a tag would be noise for the caller — a small options bag, an internal helper. It is a lightweight way to make an invalid combination un-compilable. Prefer a tagged union when the alternatives represent distinct *modes* with different downstream behaviour, when there are more than a few, or when consumers need to branch on them. A literal discriminant gives clean narrowing, readable errors, and room to add a fourth case without editing the other three. The `?: never` trick is the right size for small exclusive pairs and the wrong size for a domain model. ## A related use of never in object types The same idea appears for closing a type off entirely: a property typed `never` (not optional) in a type intended for internal use makes that shape impossible to construct externally, and `Record<string, never>` describes an object with no usable properties — a spelling occasionally used for "empty object" props. Both rest on the identical fact: a slot typed `never` accepts nothing.

  • Why is `{ value: "x", defaultValue: undefined }` still accepted?
    Because the optional marker unions `undefined` into the declared type, so `defaultValue?: never` permits `never | undefined`, which is just `undefined`. Absent and present-but-undefined are treated as the same state by default. Turning on `exactOptionalPropertyTypes` separates them, so an explicitly-undefined property no longer satisfies an optional declaration.
  • Would the union reject the extra key even without the `?: never` members?
    No. An object with an extra compatible property normally satisfies a type, so `{ value, defaultValue }` would match the first branch. Excess property checks would flag a fresh object literal, but not a value passed through a variable — which is exactly the case the `?: never` ban still catches.
  • When would you choose a discriminated union over this pattern?
    When the alternatives are real modes rather than just "one field or the other": more than two or three cases, different downstream behaviour, or consumers that need to branch. A literal tag gives clean narrowing, an error message that names the mode, and room to add a case without editing every existing branch.

saying these in an interview costs you the question

  • Thinks a property typed never can hold undefined by itself
  • Expects the compiler error to mention mutual exclusivity
  • Believes the pattern blocks both keys at runtime
  • Assumes excess property checks already prevent the bad object
  • Scales the pattern to many options without maintaining every ban

context