null vs undefined in JavaScript
The difference between null and undefined in JavaScript is intent. undefined usually means JavaScript could not find a value. null usually means your code, an API, or the browser deliberately put "no value" there. They often travel together, but they are not the same signal.
Treating them as identical is how missing data turns into polite little bugs that sit quietly until a user finds them.
Contents
- The short version
- Where undefined comes from
- Where null comes from
- typeof null is object
- Equality checks
- Default values use undefined
- Use ?? for nullish fallbacks
- JSON treats them differently
- When to return null or undefined
- Key Takeaways
The short version
Use this mental model:
| Value | Usual meaning | Common source |
|---|---|---|
undefined | No value was assigned or found | Missing property, missing argument, no return value |
null | Empty on purpose | Explicit assignment, browser APIs, database/API data |
let draftTitle;
const selectedUser = null;
console.log(draftTitle); // undefined
console.log(selectedUser); // null
draftTitle exists, but no value has been assigned. selectedUser has been assigned a value, and that value intentionally means "no selected user."
The Empty on Purpose exercise gives you a small practice version of this distinction.
Where undefined comes from
undefined is JavaScript's default missing-value result. You get it when the language has no specific value to give you.
A declared variable without an assignment is undefined:
let status;
console.log(status); // undefined
A missing object property is undefined:
const user = {
name: "Ada",
};
console.log(user.email); // undefined
An array index that does not exist is undefined:
const lessons = ["Variables", "Functions"];
console.log(lessons[5]); // undefined
A function with no return statement returns undefined:
function logMessage(message) {
console.log(message);
}
const result = logMessage("Saved");
console.log(result); // undefined
A missing function argument is undefined:
function greet(name) {
console.log(name);
}
greet(); // undefined
That is a lot of places, but the pattern is consistent: JavaScript reached for a value and came back empty-handed.
Where null comes from
null is usually deliberate absence.
let selectedUser = null;
selectedUser = {
id: "u1",
name: "Mira",
};
selectedUser = null;
That code says a user can be selected, and later the selection can be cleared. null is part of the state model.
Browser APIs often use null when a requested thing does not exist:
const button = document.querySelector("#save-button");
console.log(button); // Element or null
querySelector returns null when there is no matching element. That is different from a missing property that happens to be undefined. The API chose a clear "not found" value.
localStorage.getItem() does something similar:
const savedTheme = localStorage.getItem("theme");
console.log(savedTheme); // string or null
If the key is missing, the result is null.
For more DOM-specific debugging, see Why querySelector Returns Null. For storage examples, see How to Store Objects in localStorage with JSON.
typeof null is object
This one is annoying enough to deserve its own section:
typeof undefined; // "undefined"
typeof null; // "object"
typeof null returning "object" is a long-standing JavaScript behavior. Do not use typeof value === "object" by itself to prove that a value is a usable object.
This check is safer:
if (value !== null && typeof value === "object") {
console.log("A non-null object");
}
Without the value !== null part, null walks through the door wearing an object costume. Very rude, but historically accurate.
Equality checks
Strict equality keeps the values separate:
null === undefined; // false
null !== undefined; // true
Loose equality treats them as a pair:
null == undefined; // true
null == 0; // false
undefined == 0; // false
Most code should prefer strict equality because it avoids broad coercion. There is one common deliberate exception:
if (value == null) {
console.log("value is null or undefined");
}
That check catches only null and undefined from the usual primitive missing values. It does not catch 0, false, or "".
If your team avoids loose equality completely, write the longer version:
if (value === null || value === undefined) {
console.log("value is null or undefined");
}
Both versions are much narrower than a truthy check.
if (!value) {
console.log("This also catches 0, false, and empty strings");
}
The Truthy and Falsy Values guide covers that trap in more detail.
Default values use undefined
Default parameters apply when an argument is undefined, not when it is null.
function createPage(title = "Untitled") {
return title;
}
createPage(); // "Untitled"
createPage(undefined); // "Untitled"
createPage(null); // null
Object destructuring defaults behave the same way:
const settings = {
theme: null,
};
const { theme = "system", language = "en" } = settings;
console.log(theme); // null
console.log(language); // "en"
language was missing, so the default applied. theme existed and was explicitly null, so JavaScript kept null.
This is useful once you know it. It is also why destructuring defaults can look broken if you expected null to mean "please use the default."
Use ?? for nullish fallbacks
Nullish coalescing, ??, falls back only when the left side is null or undefined.
const displayName = user.name ?? "Anonymous";
That is different from ||, which falls back for any falsy value.
const savedCount = 0;
const countWithOr = savedCount || 10;
const countWithNullish = savedCount ?? 10;
console.log(countWithOr); // 10
console.log(countWithNullish); // 0
Use ?? when 0, false, or "" are valid values and only null or undefined should trigger the fallback.
Optional chaining also uses the nullish pair:
const city = user?.profile?.address?.city;
If any step before city is null or undefined, the expression stops and returns undefined instead of throwing.
const city = user?.profile?.address?.city ?? "Unknown";
That pattern is common when reading nested API data.
JSON treats them differently
JSON.stringify() keeps null object properties, but drops undefined object properties.
const payload = {
name: "Ada",
nickname: null,
timezone: undefined,
};
console.log(JSON.stringify(payload));
// {"name":"Ada","nickname":null}
In arrays, undefined becomes null:
console.log(JSON.stringify([1, undefined, 3]));
// [1,null,3]
That matters when you send data to an API. null often communicates "clear this field" or "this field is intentionally empty." undefined often communicates nothing because the property may not be serialized at all.
When to return null or undefined
Pick one contract and make it boring.
Use undefined when you are matching JavaScript's built-in "not found" behavior:
function findUser(users, id) {
return users.find((user) => user.id === id);
}
Array.prototype.find() returns the matching item or undefined, so passing that through is reasonable.
Use null when your application wants an explicit empty result:
function firstOverdueInvoice(invoices) {
const invoice = invoices.find((item) => item.daysLate > 0);
return invoice ?? null;
}
That function promises an invoice object or null. Callers do not need to remember that an array method is hiding inside.
The important part is not that one value is morally superior. The important part is that the return contract is clear and tests cover the empty case.
Key Takeaways
undefinedusually means JavaScript has no assigned or found value.nullusually means absence was chosen deliberately by your code or an API.typeof nullis"object", so checkvalue !== nullbefore treating a value as an object.- Defaults in parameters and destructuring apply to
undefined, notnull. - Use
??when onlynullandundefinedshould trigger a fallback.