JAVASCRIPT / VALUES, TYPES, AND COERCION
undefined against null
Distinguish undefined from null by origin and behaviour, predict when defaults and ?? fire, and choose the right one for empty fields in your own data.
What you will learn
- Trace an absent value back to its source: engine-produced undefined or code-assigned null
- Predict default parameters and destructuring defaults: they fire on undefined only
- Use ?? and ?. to cover both nullish values without disturbing 0, "" or false
- Know that JSON.stringify drops undefined-valued keys but keeps null ones
Understanding undefined against null
JavaScript ships two values that both mean "nothing here", and they differ by who produced them. undefined is what the engine hands back when a slot exists but was never filled: a variable declared without an initializer, an argument the caller omitted, a property the object never had, or the result of a function that ends without a return statement. null never appears on its own; it is written by a programmer or returned by an API that decided to report emptiness deliberately.
A useful phrasing is that undefined means nobody has said anything about this slot yet, while null means someone said, explicitly, nothing. The language leans on that difference: default parameters and destructuring defaults substitute only when the value they read is undefined, so passing null replaces the default with a real, empty value instead of triggering the fallback. The asymmetry reaches down into the grammar too, since null is a literal keyword while undefined is just a global property that happens to hold the undefined value.
Real code sees both, because different APIs picked different conventions, which is why the nullish operators ?? and ?. are specified against the pair rather than against either value alone. Serialization is where the distinction bites hardest: JSON has no undefined, so JSON.stringify deletes properties whose value is undefined and preserves ones set to null, meaning a round trip can silently lose a key. The practical rule is to pick one of the two to mean "empty" in your own data, usually null, and let undefined arrive only from the engine.
let notAssigned;
const user = { name: "Ada", nickname: null };
function greet(title = "friend") {
return `Hello, ${title}`;
}
console.log(notAssigned);
console.log(user.age);
console.log(user.nickname);
console.log(greet(undefined));
console.log(greet(null));
console.log(JSON.stringify({ a: undefined, b: null }));undefined is the absence the engine produces by default, null is the absence a programmer assigns on purpose, and only undefined activates JavaScript's default-value machinery.
Worked examples
Nullish operators cover both
?? and ?. react to null and undefined alike, and to nothing else.
const settings = { theme: null, fontSize: 0 };
console.log(settings.theme ?? "dark");
console.log(settings.margin ?? "dark");
console.log(settings.fontSize ?? 12);
console.log(settings.font?.family);Example explained
Line 1settings.theme holds null, a deliberate emptiness, so ?? falls through to "dark".
Line 2settings.margin was never defined, so the read yields undefined, which ?? also treats as nullish: both absences take the same branch.
Line 3fontSize is 0, which is neither null nor undefined, so ?? keeps it untouched.
Line 4settings.font is undefined, so ?. stops the chain and the whole expression is undefined rather than an error on .family.
Missing key versus null value
A property set to null still exists; comparing values cannot tell you that, but in can.
function noReturn() {}
const record = { email: null };
console.log("email" in record, record.email);
console.log("phone" in record, record.phone);
console.log(Object.keys(record).length);
console.log(noReturn() === undefined);Example explained
Line 1"email" in record is true even though the value is null, because null occupies the slot.
Line 2record.phone has no slot at all, so reading it produces undefined and the in check is false.
Line 3Object.keys counts one key, confirming a null-valued property is a real property.
Line 4noReturn ends without a return statement, and such a call evaluates to undefined, never to null.
Which one an API hands back
Older APIs and the prototype chain signal failure with null, while newer array methods use undefined.
console.log(JSON.parse("null"));
console.log("abc".match(/z/));
console.log([1, 2, 3].find(n => n > 5));
console.log(Object.getPrototypeOf(Object.prototype));Example explained
Line 1JSON defines a null literal but has no undefined, so parsing "null" produces the null value.
Line 2match reports no match with null, a convention inherited from the earliest browser APIs.
Line 3find reports "no element matched" with undefined, because a matching element could itself have been null.
Line 4The prototype chain terminates at null, the language's way of saying there is no further object.
Important notes
undefined is not a reserved word, so let undefined = "oops" inside a function is legal and shadows the real value; null is a literal and cannot be shadowed, which is why cautious code sometimes compares against void 0.
The two stop behaving alike the moment arithmetic touches them: Number(null) is 0, so null + 1 is 1, while Number(undefined) is NaN, so undefined + 1 is NaN.
Common mistakes
Passing null where a default is expected: greet(null) prints "Hello, null" because default parameters check for undefined only, so the fallback silently never runs.
Clearing a field with user.email = undefined: the key survives, so "email" in user stays true and Object.keys still counts it, yet JSON.stringify drops it and the receiver sees no field at all.
Guarding with value === null when the value can also be undefined: half the empty cases skip the guard and the next property access throws.
Try it yourself
Change, predict, then run
Write function label(text = "none") { return text; } and log label(), label(undefined), label(null), then log a second version that returns text ?? "none" for the same three calls. Note which single call changes and explain why.
Open the JavaScript workspaceCheck your understanding
Given function connect({ port = 8080 } = {}) { return port; }, what does console.log(connect({ port: null }), connect({}), connect()) print?
- 8080 8080 8080
- null 8080 8080
- null null 8080
- TypeError: Cannot destructure property 'port' of undefined
Show answer
A destructuring default is applied only when the value read is undefined, so { port: null } supplies a real value and the default is skipped, while { port: undefined-by-absence } and the missing argument (covered by the = {} default) both reach 8080. The tempting "8080 8080 8080" assumes null counts as absent, which is exactly the substitution JavaScript refuses to make.