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?
answer
- two sides: writing and reading
- the key may be omitted entirely
- reads widen with undefined
- delete refuses required properties
- nothing of it survives compilation
basics
~20 sMarking 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
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.
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.
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.
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