In a TypeScript object type, what is the difference between declaring `email?: string` and declaring `email: string | undefined`?
answer
- presence versus value
- one may be omitted, one must be written
- reads are identical either way
- spread and Object.keys tell them apart
- required field forces a decision
basics
~20 sThe optional form lets the key be omitted; the union form requires the key to be present, even if its value is undefined. Both read as string | undefined, and under default settings the optional form also accepts an explicit undefined.
solid answer
~40 sThey differ on the producing side, not the reading side. `email?: string` means the key may be absent, so `{ }` satisfies it. `email: string | undefined` means the key is required and must be written out, so `{ }` is an error and you have to type `{ email: undefined }`. Reads give `string | undefined` in both cases, so consuming code looks identical. Under default compiler settings the optional form is strictly the more permissive of the two, because it also accepts an explicitly present `undefined` — that overlap is exactly what `exactOptionalPropertyTypes` removes. The union form is the right choice when you want to force callers to make a conscious decision about a field, since forgetting it becomes a compile error instead of silently defaulting to absent.
code
typescript · 12 linestype A = { email?: string };
type B = { email: string | undefined };
const a1: A = {}; // ok
const b1: B = { email: undefined }; // ok — key must be written
const defaults = { email: '[email protected]' };
const omitted: A = {};
const explicitUndefined: B = { email: undefined };
console.log({ ...defaults, ...omitted }.email); // '[email protected]'
console.log({ ...defaults, ...explicitUndefined }.email); // undefinedgo deeper
Remember the core split: with ? you may leave the key out, with | undefined you must still write the key even when the value is undefined.
Explain that reads are identical and only construction differs, and be ready to demonstrate the runtime difference with in, Object.keys, or a spread that overwrites a default.
Argue the design consequence: a required | undefined field turns "someone forgot this" into a compile error, which is what you want for records merged over defaults or sent as patches.
Own the API-evolution angle — adding an optional field is invisible to every existing caller, while adding a required one is a deliberate breaking change you can stage across a codebase.
## Same read type, different obligations The confusion here comes from the fact that the two declarations look the same from the consumer's chair and completely different from the producer's. ```ts type A = { email?: string }; type B = { email: string | undefined }; declare const a: A; declare const b: B; a.email; // string | undefined b.email; // string | undefined ``` Reading is identical. Construction is not: ```ts const a1: A = {}; // ok — key may be omitted const a2: A = { email: undefined }; // ok by default const b1: B = {}; // error: 'email' is missing const b2: B = { email: undefined }; // ok — key present, value undefined ``` So `?` relaxes *presence*, while `| undefined` relaxes only *value*. Under default settings `?` implies the union as well, which makes type `A` strictly more permissive than type `B`. ## Why the distinction is worth having The union form is a forcing function. If a type says `email: string | undefined`, every construction site must mention `email` — the author has to look at the field and decide. Adding a new field to such a type is a breaking change that lights up every caller, which is often exactly what you want for a configuration object or a domain record where silently omitting a field is a bug. With `?`, adding a field is invisible: every existing caller keeps compiling and every existing object quietly lacks the new data. The optional form is the right model when absence is genuinely meaningful and common — query parameters, partial patches, options bags where most callers supply two of fifteen fields. ## The runtime shapes really are different This is not a type-layer nicety. A key that is present holding `undefined` and a key that is absent behave differently in JavaScript: ```ts const present = { email: undefined }; const absent: { email?: string } = {}; 'email' in present; // true 'email' in absent; // false Object.keys(present); // ['email'] Object.keys(absent); // [] ``` Spreading inherits that difference, and it is a classic production bug: ```ts const defaults = { email: '[email protected]' }; const patch = { email: undefined }; ({ ...defaults, ...patch }).email; // undefined — the key was present and won ``` If `patch` had simply omitted the key, the default would have survived. Any merge-based update path — options merging, PATCH request bodies, reducer state — cares about which of the two you produced. That is why the distinction is not academic and why interviewers ask it. ## Where the two blur, and how to unblur them By default the checker treats `email?: string` as *may be missing or may be present-undefined*, so it cannot help you keep those apart. The `exactOptionalPropertyTypes` option narrows `?` to mean only *may be missing*, at which point `{ email: undefined }` is rejected for `A` and you have to write `email?: string | undefined` to allow it. That gives you three distinguishable declarations instead of two: - `email?: string` — absent, or a string. No explicit `undefined`. - `email?: string | undefined` — absent, explicit `undefined`, or a string. - `email: string | undefined` — always present; `undefined` or a string. ## Assignability between them `B` is assignable to `A`: a value that always has the key, possibly `undefined`, satisfies a type that permits the key to be missing. The reverse fails, because an `A` may lack the property entirely. Generic code that accepts `A` therefore also accepts a `B`, which is worth remembering when designing a shared parameter type. ## Interview framing Lead with the one-line discriminator — *optional controls whether the key must exist; the union controls only what value it may hold* — then prove you understand the consequences by naming the `in` / `Object.keys` / spread differences. A candidate who stops at "they're basically the same because both read as `string | undefined`" has answered only half the question.
- Which of the two types is assignable to the other?`{ email: string | undefined }` is assignable to `{ email?: string }` — always having the key satisfies a type that merely permits it. The reverse is rejected, because an optional-property value may lack the key entirely and the target requires it. So the optional form is the wider type under default settings.
- When would you deliberately choose the required `| undefined` form?When forgetting the field should be a compile error rather than a silent absence — configuration objects, domain records, and anything merged over defaults. Every construction site must then name the field, so adding a new one breaks callers loudly instead of leaving them quietly missing data.
- Why can spreading an object with an explicitly undefined property break a defaults merge?Because spread copies keys, not defined values. A present key holding `undefined` overwrites the default that came before it, while an omitted key leaves the default intact. Merge code that assumes "undefined means no opinion" needs either omitted keys or an explicit filter before the spread.
saying these in an interview costs you the question
- Says the two declarations are interchangeable
- Thinks the union form also allows omitting the key
- Claims the read type differs between the two
- Assumes spreading a present-undefined key is a no-op
- Believes only the optional form ever accepts undefined