In TypeScript, a value of type `{ id: string; name?: string }` is assigned to a variable of type `{ id: string; name: string }`. Does it compile, and does the reverse assignment compile?
answer
- optional is a weaker promise
- presence, then type
- weaker cannot satisfy stronger
- required over-delivers on optional
basics
~20 sThe first fails: a member that is optional on the source may be absent, so it cannot satisfy a required member on the target. The reverse compiles, because a required member always satisfies an optional one.
solid answer
~50 sOptional members make assignability directional. Going from `{ id: string; name?: string }` to `{ id: string; name: string }` is rejected — the source only promises `name` *might* be there, and its type is effectively `string | undefined`, neither of which satisfies a target member that must be present and must be a `string`. The compiler says as much: the property is optional in the source type but required in the target. The reverse assignment is fine, because a source that always has `name: string` over-delivers on a target that merely allows it. The same logic explains why `{ id: "a" }` alone is assignable to `{ id: string; name?: string }`: omitting an optional member is exactly what optional means. If you need the strict version, narrow the value first with a check or a guard rather than reaching for an assertion.
go deeper
Remember that a question mark means the member may be missing entirely, so a value typed that way cannot be handed to something that demands the member is always there. The other direction is fine.
Explain both halves of the failure — the member may be absent, and reading it yields string | undefined — and state the direction as a rule about obligations: a weaker promise cannot discharge a stronger one.
Show the disciplined fix: narrow with a real check and construct the stricter object explicitly, and question whether two states that differ in guarantees should be one type with an optional member at all.
Own the modelling call — optional members quietly encode a state machine in a single type. Decide when separate types per state, or a discriminated shape, buys enough clarity to justify the extra declarations across a codebase.
## The two questions the compiler asks about an optional member When the target declares `name: string` and the source declares `name?: string`, two separate things are wrong at once: 1. **Presence.** `?` means the member may be absent from the object entirely. The target requires it to be there. 2. **Type.** Reading an optional member yields `string | undefined`, and `undefined` is not assignable to `string` under `strictNullChecks`. Either one alone would sink the assignment. The compiler reports the presence problem specifically, with a message along the lines of *property 'name' is optional in type A but required in type B* — a message worth recognising, because it tells you the fix is about the modifier, not about the member's type. ```ts interface Draft { id: string; name?: string } interface Published { id: string; name: string } declare const d: Draft; const p: Published = d; // error — optional in source, required in target declare const q: Published; const e: Draft = q; // ok — required over-delivers on optional ``` ## The direction, stated once Optional loosens the *target* and weakens the *source*: - Target optional, source required → **fine**. The target permits absence; the source is simply never absent. - Target optional, source absent → **fine**. That is the whole point of `?`. - Target required, source optional → **error**. The source cannot promise presence. - Target required, source absent → **error**, for the same reason, more obviously. This is the same "more is fine, less is not" direction that governs extra members, applied to a member that may or may not be there. An optional member is a *weaker* obligation, and a weaker obligation cannot discharge a stronger one. ## Fixing it in real code The wrong fix is `d as Published`. An assertion performs no check and emits no code; it just instructs the compiler to stop objecting, and the missing `name` will surface later as `undefined` in a place that expects a string. The honest fixes: ```ts function publish(d: Draft): Published | null { return d.name === undefined ? null : { id: d.id, name: d.name }; } ``` Here the check narrows `d.name` to `string`, and the new object is built with the member definitely present. Alternatively, model the two states as separate types from the start — if a draft and a published record differ in what is guaranteed, that difference is real domain information, and an optional member is a lossy way to express it. ## The `exactOptionalPropertyTypes` wrinkle By default, `name?: string` accepts an explicitly written `undefined`: `{ id: "a", name: undefined }` is a valid `Draft`. With the `exactOptionalPropertyTypes` compiler flag on, the modifier means *may be absent* but not *may be present and undefined*, so that same object is rejected and you must omit the member instead. The flag does not change the direction described above; it tightens what counts as satisfying the optional member itself. ## Why this shows up in interviews It is the smallest case where structural assignability is visibly asymmetric, and candidates who have only internalised "TypeScript compares shapes" often expect both directions to work. Saying the direction crisply, and explaining it in terms of what each side *promises to a reader*, demonstrates that you understand assignability as a relation between obligations rather than a shape-equality test.
- Why is `as` the wrong way to make the rejected assignment compile?Because an assertion is not a conversion. It performs no check, emits nothing, and simply suppresses the error — the object may still arrive with `name` absent, and the first code that treats it as a `string` gets `undefined`. Narrowing with a real check, or constructing the target object explicitly, keeps the guarantee the target's type is claiming.
- If both types declare `name?: string`, is the assignment symmetric?Yes. Identical modifiers on both sides means each obligation is the same strength in each direction, so the members impose no constraint beyond their types being mutually assignable. Asymmetry appears only when the modifier differs, or when the member types themselves are related in one direction only.
- What does `exactOptionalPropertyTypes` change about a member declared `name?: string`?Without it, the member may be absent *or* explicitly set to `undefined`. With it on, only absence is allowed — writing `{ name: undefined }` no longer satisfies the type, and you must leave the member out. It makes "missing" and "present but undefined" distinguishable, which matters for code that uses the `in` operator or enumerates keys.
saying these in an interview costs you the question
- Thinks optional and required members are interchangeable both ways
- Says adding `as` fixes it because the shapes match anyway
- Believes `name?: string` means the member is always present
- Claims the error is about the member's type rather than its presence
- Fixes it by making the target member optional and moving the bug downstream