Knowledge Base/concepts/null vs undefined in JavaScript

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

Use this mental model:

ValueUsual meaningCommon source
undefinedNo value was assigned or foundMissing property, missing argument, no return value
nullEmpty on purposeExplicit 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

  • undefined usually means JavaScript has no assigned or found value.
  • null usually means absence was chosen deliberately by your code or an API.
  • typeof null is "object", so check value !== null before treating a value as an object.
  • Defaults in parameters and destructuring apply to undefined, not null.
  • Use ?? when only null and undefined should trigger a fallback.
Share this article:
javascriptfundamentalstypesdebugging