Skip to Content
Optional Properties vs undefined: Which One You Meant
Beginner
0 likes
Tutorials

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 | null if your data uses null. 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
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.