JavaScript fundamentals comparison

null vs undefined

JavaScript is unusual in having two distinct "empty" values. Mixing them up causes subtle bugs — and explaining the difference clearly is a common code review and interview moment at every level.

TL;DR

  • undefined — the runtime produces this automatically. A declared but unassigned variable, a missing function argument, or a non-existent object property are all undefined.
  • null — a developer assigns this explicitly to signal intentional absence: "there is no value here, and I chose that deliberately."
  • Loosely equal, strictly not. null == undefined is true; null === undefined is false.
  • Convention: never assign undefined by hand; use null whenever you want to represent an intentionally absent value.

Side-by-side comparison

Aspectundefinednull
Meaning Unintentional absence — the runtime has not assigned a value Intentional absence — the developer explicitly signals "no value"
typeof "undefined" "object" (historical bug — the actual type is null)
Source Runtime-produced: unassigned vars, missing args, missing properties, void functions Developer-assigned: an explicit marker for "nothing here"
Loose equality (==) undefined == nulltrue null == undefinedtrue
Strict equality (===) undefined === nullfalse null === undefinedfalse
JSON serialisation Property is omitted from the JSON output Property is preserved as null in the JSON output
Arithmetic coercion undefined + 1NaN null + 11 (null coerces to 0)
TypeScript type undefined — usually from optional properties (?:) null — usually from nullable fields (T | null)
Best practice Let the runtime produce it; avoid assigning it explicitly Use to represent an intentionally absent or reset value

What is undefined?

undefined is JavaScript's way of saying "this variable exists but has not been given a value yet." The runtime produces it in four common situations:

  • Unassigned variable. let count;count is undefined until you assign it.
  • Missing function argument. Calling fn() when the function expects a parameter leaves that parameter as undefined inside the function body.
  • Missing object property. Reading a property that does not exist on an object returns undefined rather than throwing.
  • Void function return. Any function that reaches its end without a return statement implicitly returns undefined.

The void operator — as in void 0 — also always evaluates to undefined. It appeared frequently in older minified code as a guaranteed way to obtain undefined before it became a read-only value.

{`let x;
console.log(x);           // undefined (unassigned)

function greet(name) {
  console.log(name);      // undefined if not passed
}
greet();

const obj = {};
console.log(obj.age);     // undefined (no such property)

function noReturn() {}
console.log(noReturn());  // undefined (no return statement)

console.log(void 0);      // undefined`}

What is null?

null is a deliberate assignment. When a developer writes const user = null, they are explicitly communicating: "I know this variable exists, and right now there is no user." This intentional quality is what distinguishes it from undefined.

Common situations where null is the right choice:

  • An optional object that is absent. let currentUser = null — before the user logs in.
  • Resetting a reference. Setting a variable to null severs the reference and allows the garbage collector to reclaim the object.
  • API and database conventions. REST and GraphQL APIs commonly return null for optional fields that were not populated. SQL databases have a native NULL concept that maps naturally to JavaScript's null.
  • Sentinel return values. A function that searches for an item and finds nothing can return null to signal "not found" without throwing an exception.
{`let currentUser = null; // not logged in yet

async function fetchUser(id) {
  const row = await db.find(id);
  return row ?? null;   // explicit "not found"
}

// Resetting a reference
let heavyObject = buildCache();
heavyObject = null;     // GC can now reclaim the memory

// TypeScript: optional vs nullable
type Config = {
  timeout: number | null;  // deliberately unset — null
  retries?: number;        // may be undefined — optional
};`}

Key operators: optional chaining and nullish coalescing

Two operators introduced in ES2020 make working with null and undefined significantly safer and more readable.

Optional chaining (?.)

Accesses a property or calls a method only if the left-hand side is not nullish. If it is null or undefined, the entire expression short-circuits to undefined instead of throwing a TypeError.

{`const city = user?.address?.city;
// If user or address is null/undefined → city is undefined
// No TypeError thrown

const len = arr?.length;
// Safe even if arr is null

user?.save();            // Call only if user is not nullish`}

Nullish coalescing (??)

Returns the right-hand side when the left-hand side is null or undefined. Unlike the logical OR operator (||), it does not trigger on other falsy values such as 0, "", or false.

{`const name = user?.name ?? 'Anonymous';
// Falls back only on null/undefined — not on ""

const count = options.retries ?? 3;
// retries === 0 → keeps 0 (unlike || which would give 3)

// Combining both:
const city = user?.address?.city ?? 'Unknown';`}

JSON serialisation difference

{`const a = { name: 'Alice', age: undefined };
const b = { name: 'Alice', age: null };

JSON.stringify(a); // '{"name":"Alice"}'        — age omitted
JSON.stringify(b); // '{"name":"Alice","age":null}' — age preserved

// Practical consequence: use null if the field must appear
// in the serialised payload; leave undefined if absence is fine.`}

TypeScript and strictNullChecks

By default in older TypeScript configurations, null and undefined could be assigned to any type, which masked bugs. The strictNullChecks compiler option (enabled automatically by strict: true) makes them distinct types.

With strictNullChecks on:

  • A string variable cannot hold null or undefined unless the type is explicitly declared as string | null or string | undefined.
  • The compiler requires you to narrow or guard before using a potentially-null value, eliminating null-dereference bugs at compile time.
  • Optional properties (prop?: T) have type T | undefined; nullable properties (prop: T | null) have type T | null — a meaningful semantic distinction.
{`// strictNullChecks: true

function getLength(s: string | null): number {
  // s.length  ← error: object is possibly null
  return s?.length ?? 0;  // safe with optional chaining + ??
}

// Optional vs nullable
type Profile = {
  bio?: string;          // string | undefined — field may not exist
  avatarUrl: string | null; // field exists but may be deliberately absent
};`}

If you are starting a new project, enable TypeScript with strict: true from day one. Retrofitting strictNullChecks onto a large existing codebase is a significant effort.

How engineers talk about null vs undefined

These are phrases you will hear in code reviews, stand-ups, and technical interviews. Knowing them helps you follow discussions and contribute confidently.

Phrases about undefined

  • "The property comes back as undefined because it's not in the response schema."
  • "You're reading config.timeout before the config object has been initialised — it's still undefined."
  • "Optional chaining short-circuits to undefined if any link in the chain is nullish."
  • "Use a default parameter to guard against undefined arguments: function fn(x = 0)."

Phrases about null

  • "Return null rather than throwing when the record isn't found — let the caller decide."
  • "The API sends back null for optional fields that haven't been populated."
  • "Use the nullish coalescing operator (??) to fall back when a value is null or undefined."
  • "Set the ref to null after unmount so the DOM node can be garbage collected."

General phrases about both

  • "Check value == null — the loose equality catches both null and undefined in one guard."
  • "strictNullChecks is turned off — that's why TypeScript isn't catching the potential null dereference."
  • "The typeof null bug is a legacy quirk from the original JavaScript implementation — always use === null to check for null."

Decision guide

  • Variable declared but no value assigned yet → runtime produces undefined automatically
  • Object property that does not exist → runtime produces undefined
  • You want to say "no value here, intentionally" → assign null
  • Check for both null and undefined at once → use value == null or use ?? / ?.
  • Field must appear in JSON output with an explicit empty marker → use null (undefined is omitted)
  • Field should be absent from JSON output entirely → leave as undefined
  • TypeScript optional field (?:) vs nullable field (| null) → choose based on whether absence is a deliberate runtime state or merely "not provided"
  • Providing a fallback value without replacing 0 or "" → use ?? not ||
  • Navigating a chain of objects safely → use optional chaining (?.)

Frequently asked questions

Why does typeof null return 'object'?

This is a well-known historical bug in JavaScript that has never been fixed for backwards-compatibility reasons. In the original implementation, values were stored as type tags paired with the actual value. The null pointer was represented as all-zero bits — the same bit pattern used for objects — so typeof null returned 'object'. The ECMAScript committee considered fixing this in ES6 but ultimately decided the breakage would be too widespread. The correct check for null is value === null.

Are null and undefined loosely equal?

Yes — null == undefined evaluates to true because of JavaScript's abstract equality algorithm. This is one of the very few cases where using == instead of === is intentional: if (value == null) catches both null and undefined in a single concise check. Strictly speaking, null === undefined is false, because they are different types.

How do I check for both null and undefined at once?

The cleanest approach is the loose equality check: if (value == null). Because of JavaScript's abstract equality rules, this evaluates to true for both null and undefined — and only for those two values. Alternatively, use the nullish coalescing operator (??) or optional chaining (?.) which both treat null and undefined as 'no value'. Avoid checking with !value because that also catches 0, empty string, and false.