skip to content

In a TypeScript interface, what does the `?` in `email?: string` change — for code that builds the object, for code that reads the property, and for the emitted JavaScript?

level: juniorimportance: must knowfreq 80%

answer

  1. two sides: writing and reading
  2. the key may be omitted entirely
  3. reads widen with undefined
  4. delete refuses required properties
  5. nothing of it survives compilation

basics

~20 s

Marking a property with ? makes it optional: an object may omit the key, and reading it yields string | undefined under strictNullChecks. The modifier is compile-time only and changes nothing in the emitted JavaScript.

solid answer

~40 s

`?` weakens the property on both sides of the contract. On the producing side, a value of that type may leave the key out entirely — `const u: User = { id: 1 }` type-checks, while omitting a required `id` would not. On the consuming side, the compiler widens what you read: with `strictNullChecks` on, `u.email` has type `string | undefined`, so every read must handle absence (`u.email ?? 'none'`, `if (u.email)`, `u.email?.toLowerCase()`). The modifier also makes `delete u.email` legal — deleting a required property is a compile error. None of this exists at runtime: the interface is erased and the emitted JavaScript has no trace of the `?`, so an object that arrives from `JSON.parse` can violate the declaration freely.

go deeper

for a junior

Be able to say plainly that ? lets the key be left out and that reading such a property gives you the value or undefined, so you must guard it with optional chaining or a check.

for a middle

Explain the two-sided effect — producers may omit, consumers read T | undefined — and connect it to strictNullChecks, since without that flag the read type never widens and the guarantee evaporates.

for a senior

Show that the modifier is erased and therefore says nothing about data arriving from the network or an untyped library; describe where you validate at the boundary instead of trusting the declaration.

for a principal

Own the modelling call: whether a field's absence, its null, and its empty value are three states worth distinguishing in your domain types, and what that choice costs every consumer downstream.

## What `?` actually declares An object type is two contracts in one: what a *producer* must supply when it builds a value, and what a *consumer* may assume when it reads one. The `?` modifier relaxes both at the same time. ```ts interface User { id: number; email?: string; } ``` For the producer, the key may be missing: `const u: User = { id: 1 }` compiles, whereas dropping `id` produces *Property 'id' is missing in type '{}' but required in type 'User'*. For the consumer, the property type is no longer `string`. With `strictNullChecks` enabled, `u.email` has type `string | undefined`, because there may be nothing there to read. That widening is the entire payoff — the checker now forces the absent case to be handled at every read site: ```ts u.email.toLowerCase(); // error: possibly 'undefined' u.email?.toLowerCase(); // ok const e = u.email ?? 'none'; // ok, e: string ``` If `strictNullChecks` is off, the read type stays `string`, the guard is not required, and the same code that type-checks will throw at runtime on a missing key. Optional properties are only as useful as that flag. ## Optional is not the same as nullable `email?: string` says *the key may be absent*. `email: string | null` says *the key is always there and may hold `null`*. They are different runtime shapes and different obligations on the producer, and mixing them up is the most common beginner mistake here. A wire format that sends `"email": null` is modelled by the union; one that omits the field is modelled by `?`. ## Optional methods and `delete` The modifier is not restricted to data properties. A method member can be optional too, and calls to it must be guarded: ```ts interface Handler { onClose?(): void; } declare const h: Handler; h.onClose?.(); // ok — no call when it is absent ``` Optionality is also what unlocks `delete`. With `strictNullChecks` on, `delete u.id` is rejected with *The operand of a 'delete' operator must be optional*, because removing the key would leave a value that no longer matches its declared type. `delete u.email` is fine. ## Assignability follows the same rule Because a required property satisfies everything an optional one asks for, `{ id: number; email: string }` is assignable to `User`. The reverse is not: a `User` cannot be passed where `email` is required, since it may not have one. Optionality is a widening of the accepted set, not a cosmetic annotation. ## Nothing survives compilation An `interface` emits no JavaScript at all — declaration and modifiers alike vanish. `?` therefore adds no existence check, no default, and no cost. The practical consequence is that the guarantee only covers values the checker actually saw. A parsed JSON payload, an `any` from an untyped library, or a value produced by a type assertion can carry any shape whatsoever; the declaration is a claim about your code, not a validation of your data. If the shape must be trusted, validate at the boundary with real runtime code. Detecting the two runtime situations — key absent versus key present holding `undefined` — needs a runtime tool, not the type. The `in` operator and `Object.hasOwn` answer that question; a plain `u.email === undefined` cannot tell them apart: ```ts const a = { email: undefined }; const b = {} as { email?: string }; 'email' in a; // true 'email' in b; // false ``` ## The looseness worth knowing about By default `?` accepts more than "may be missing": you may also write the key explicitly with the value `undefined`, so `{ id: 1, email: undefined }` is a legal `User`. That deliberate blur — absent and present-undefined treated as the same thing — is what the `exactOptionalPropertyTypes` compiler option tightens. Under default settings, treat `?` as *may be missing or explicitly undefined*. ## How to answer in an interview Name all three surfaces without prompting: the producer may omit the key, the consumer reads `T | undefined` under `strictNullChecks`, and the emitted JavaScript is untouched. That last clause is what separates a candidate who has read the handbook from one who has only used the syntax.

  • What happens to an optional property if strictNullChecks is turned off?
    The read type stops widening: `u.email` is just `string`, so unguarded access compiles and then throws at runtime when the key is missing. Without `strictNullChecks` the `?` still lets producers omit the key, but consumers get no protection at all — which is why optional properties are close to useless in a non-strict configuration.
  • How would you tell at runtime whether an optional key was omitted or explicitly set to undefined?
    Only with a runtime check — the type layer cannot see the difference. The `in` operator or `Object.hasOwn(obj, 'email')` reports whether the key exists; `obj.email === undefined` is true in both cases. `Object.keys` likewise lists a key that was written with an `undefined` value.
  • Is `email?: string` the same as `email: string | null`?
    No. `?` says the key may be absent; the union says the key is always present and may hold `null`. They describe different runtime shapes and impose different obligations on whoever constructs the object. Choose based on what the data source actually produces — an omitted JSON field versus an explicit `null`.

saying these in an interview costs you the question

  • Says optional and nullable mean the same thing
  • Thinks ? emits a runtime existence check or a default
  • Claims the read type stays string under strictNullChecks
  • Believes ? protects a shape coming from JSON.parse
  • Assumes an omitted required property is only a warning

context