Optional Properties vs undefined: Which One You Meant
See the difference between a question mark and a union with undefined, what it does at run time, and what exactOptionalPropertyTypes changes. After this, you can choose the right one for your data.
note?: string and note: string | undefined look like the same thing. They are not, and picking the wrong one causes bugs that only show up when data crosses a boundary, like a form or a JSON file. This page shows the difference with real compiler output, then gives you a rule for choosing.
The difference in one example
type WithQuestionMark = { id: number; note?: string };
type WithUndefined = { id: number; note: string | undefined };
const a: WithQuestionMark = { id: 1 };
const b: WithUndefined = { id: 2 };
const c: WithUndefined = { id: 3, note: undefined };
difference.ts(5,7): error TS2741: Property 'note' is missing in type '{ id: number; }' but required in type 'WithUndefined'.
With ?, the property may be left out. With | undefined, the property must be there, even if its value is undefined. Line 4 is fine and line 5 is refused. Line 6 is fine, because you wrote the key.
Reading either property gives you the same type, string | undefined, so the check you write before using it is identical.
Why it matters at run time
JavaScript treats a missing property and a property set to undefined differently in a few places. JSON is the most common one.
type WithQuestionMark = { id: number; note?: string };
const missing: WithQuestionMark = { id: 1 };
const explicit: WithQuestionMark = { id: 1, note: undefined };
console.log("note" in missing, "note" in explicit);
console.log(JSON.stringify(missing), JSON.stringify(explicit));
console.log(Object.keys(explicit));
false true
{"id":1} {"id":1}
[ 'id', 'note' ]
Both objects satisfy the type. At run time, one has a note key and one does not. JSON.stringify drops the key whose value is undefined, so both serialize the same, but in and Object.keys can tell them apart. Code that loops over keys, or checks in, behaves differently for the two.
Make the compiler tell them apart
TypeScript has a setting, exactOptionalPropertyTypes, that treats "left out" and "set to undefined" as different things. It is on in the tsconfig.json that tsc --init creates. It is off by default when you run tsc on a single file.
type Profile = { id: number; nickname?: string };
const a: Profile = { id: 1 };
const b: Profile = { id: 2, nickname: undefined };
exact.ts(4,7): error TS2375: Type '{ id: number; nickname: undefined; }' is not assignable to type 'Profile' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'nickname' are incompatible.
Type 'undefined' is not assignable to type 'string'.
Now nickname?: string means "a string, or not there at all". Writing undefined explicitly is refused. If you want to allow that on purpose, say so in the type: nickname?: string | undefined.
This setting is why code that compiled in a quick test can fail in a project made with --init. The setting is doing its job.
How to choose
Ask what "no value" means in your data.
- The key may not exist at all. Use
?. This fits form data where untouched fields are never sent, and option objects where you pass only what you change. - The key always exists, and "nothing" is a valid answer. Use
| undefined, or| nullif your data usesnull. This fits records where every field is always listed, so a reader never wonders if a field was forgotten.
When the value comes from JSON, remember that JSON has no undefined. A missing key and an explicit null are the two things you can actually receive. A type of string | undefined on a field that can arrive as null is a wrong claim.
⚠ Warning: Do not reach for ? just to make an error go away. Once a property is optional, every place that reads it has to handle the missing case.
A quick check you can run
Take one type from your own code and write down, in a sentence, what the optional field means when it is missing. If you cannot, the type is guessing. Try npx tsc --noEmit --exactOptionalPropertyTypes on your project and see what it finds.
Related course: TypeScript for Beginners.
About
See the difference between a question mark and a union with undefined, what it does at run time, and what exactOptionalPropertyTypes changes. After this, you can choose the right one for your data.
- Responsible
- Mary Q
- Last Update
- 10/14/2026
- Reading Time
- 4 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.