undefined Is Not the Same as Not Being There
An optional property looks a lot like a property whose value can be undefined.
But they are not quite the same thing.
That difference becomes important when you care about whether a property exists at all.
🔴 The Problem
Consider these two objects:
const a = {};
const b = {
value: undefined,
};
At a glance, they can look equivalent.
Reading value gives you undefined in both cases:
console.log(a.value);
console.log(b.value);
Both print:
undefined
But the objects are different.
One does not have a value property.
The other does.
You can see that with:
console.log("value" in a);
console.log("value" in b);
The result is:
false
true
That distinction is easy to miss because normal property access hides it.
🟢 The TypeScript Part
Now look at this type:
type Config = {
value?: string;
};
The ? means the property may be absent.
So this is valid:
const config: Config = {};
And this is also valid:
const config: Config = {
value: "hello",
};
Depending on your TypeScript configuration, this can also be valid:
const config: Config = {
value: undefined,
};
But those last two objects still represent different runtime states.
The first has a value.
The second has a property whose value is undefined.
That difference matters when the presence of the property itself has meaning.
âš¡ The Part That Usually Bites
Imagine an API where omitting a property means:
"Don't change this setting."
while explicitly passing undefined has a different meaning.
Or imagine code that checks whether a property was supplied:
if ("value" in config) {
// the caller supplied the property
}
Now these two values are no longer interchangeable:
const a: Config = {};
const b: Config = {
value: undefined,
};
The type might make them look similar.
The runtime object does not.
This becomes especially interesting when objects are passed between functions, merged together, serialized, or used as configuration.
🧠The TypeScript Detail
There is a compiler option specifically related to this distinction:
{
"compilerOptions": {
"exactOptionalPropertyTypes": true
}
}
With this enabled, an optional property means the property can be omitted.
It does not automatically mean you can explicitly assign undefined.
So:
type Config = {
value?: string;
};
allows:
const a: Config = {};
and:
const b: Config = {
value: "hello",
};
But this becomes an error:
const c: Config = {
value: undefined,
};
If you actually want to allow the property to exist with undefined, say so:
type Config = {
value?: string | undefined;
};
Now the type describes both possibilities:
property is absent
OR
property exists with undefined
OR
property exists with a string
That is a much more precise description of the object.
🧩 When Could This Be Useful?
This distinction is especially useful for configuration and update objects.
Consider:
type UserUpdate = {
name?: string;
email?: string;
};
An update function might interpret an omitted property as:
"Leave the existing value alone."
while a present property could mean:
"Change this field."
That makes property presence part of the API's meaning.
The same idea appears when dealing with object merging:
const defaults = {
color: "orange",
};
const options = {
color: undefined,
};
const result = {
...defaults,
...options,
};
The property was not missing.
It was explicitly present.
So the resulting object contains:
{
color: undefined;
}
That can be very different from simply not providing color at all.
🧠The Takeaway
An optional property is about presence.
undefined is about value.
Those two concepts often produce the same result when you simply read the property:
object.value;
But they can represent different states.
Once your application cares about whether something was omitted or explicitly provided, that difference stops being a TypeScript technicality.
It becomes part of the API design.
value?: string
does not simply mean:
"value can be undefined"
It starts with:
"this property may not exist"
And sometimes, that is exactly the distinction your code needs.
Top comments (0)