# null vs undefined in JavaScript: Differences, Checks and When to Use Each (Tested Examples)

**`undefined` means a value was never assigned (missing property, missing argument, bare `return`). `null` is an intentional empty value you assign yourself. Prefer `===` when you care which one you have, use `== null` when either should count as empty, and reach for `??` / optional chaining when you want defaults without treating `0` or `''` as missing.**

MDN documents both primitives: [null](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null) is the intentional absence of an object value, while `undefined` is the primitive for an uninitialized value. This guide shows the behaviour with real output from Node.js 24.22.1 running `test/js/demo.mjs` on this box. If loose vs strict equality still feels fuzzy, the `null == undefined` case below is one of the classic places where `==` and `===` disagree.

I also checked the same snippets mentally against everyday DOM and fetch patterns: `document.getElementById` returns `null` on a miss, optional JSON fields come back as `undefined` when absent, and many REST APIs send explicit `null` for cleared fields. Keeping those three stories straight saves a surprising number of production bugs.

**In this guide:**

- At a glance: null vs undefined
- typeof null is "object" (yes, really)
- == vs === between null and undefined
- Missing properties vs explicit null
- Default parameters: undefined triggers, null does not
- Optional chaining (?.) safely reads nested values
- Nullish coalescing (??) vs logical OR (||)
- JSON.stringify and JSON.parse quirks
- Function returns, void 0, and ReferenceError
- DOM, APIs and everyday pitfalls
- Practical checks and when to use each
- Frequently asked questions

## At a glance: null vs undefined

| Question | `null` | `undefined` |
| --- | --- | --- |
| What it means | Intentional empty | Not assigned / missing |
| `typeof` | `"object"` (historical quirk) | `"undefined"` |
| `null == undefined` | `true` | |
| `null === undefined` | `false` | |
| Default params trigger? | No | Yes |
| `??` treats as missing? | Yes | Yes |
| `||` treats as missing? | Yes (falsy) | Yes (falsy) |
| JSON | Serialized as `null` | Keys omitted / array holes become `null` |
| Typical source | You assigned it; APIs; DOM | Missing props, no `return`, sparse arrays |

Rule of thumb I use in application code: assign `null` when I want to say "I looked, there is nothing." Leave a property `undefined` (or omit it) when it was never set. Check with `=== null` / `=== undefined` when the difference matters, and with `== null` when either empty state should take the same branch.

## typeof null is "object" (yes, really)

The first surprise every JavaScript developer hits:

```
console.log("typeof null:", typeof null);
console.log("typeof undefined:", typeof undefined);
```

```
typeof null: object
typeof undefined: undefined
```

`typeof null === "object"` is a decades-old language quirk. It does *not* mean `null` is a real object you can read properties from. Accessing a property on `null` or `undefined` throws - the crash covered in [Cannot read properties of undefined](https://mfaysal.com/blog/javascript-cannot-read-properties-of-undefined).

Use `typeof` to detect `undefined`. Detect `null` with `value === null`, not with `typeof`.

## == vs === between null and undefined

Loose equality treats them as matching. Strict equality does not:

```
console.log("null == undefined:", null == undefined);
console.log("null === undefined:", null === undefined);
console.log("null == null:", null == null);
console.log("undefined == undefined:", undefined == undefined);
console.log("null == 0:", null == 0);
console.log("undefined == 0:", undefined == 0);
console.log("null == false:", null == false);
console.log("undefined == false:", undefined == false);
console.log("Boolean(null):", Boolean(null));
console.log("Boolean(undefined):", Boolean(undefined));
console.log("!null:", !null, "!undefined:", !undefined);
```

```
null == undefined: true
null === undefined: false
null == null: true
undefined == undefined: true
null == 0: false
undefined == 0: false
null == false: false
undefined == false: false
Boolean(null): false
Boolean(undefined): false
!null: true !undefined: true
```

Both are falsy, so `if (!value)` collapses them with `0`, `''` and `false`. That is often too aggressive. The idiom `if (value == null)` catches only `null` and `undefined` - useful when zero should still count as a real value. For a deeper look at loose vs strict equality, see [== vs === in JavaScript](https://mfaysal.com/blog/javascript-double-equals-vs-triple-equals).

## Missing properties vs explicit null

A property you never set is `undefined`. A property you set to `null` is present and empty:

```
const user = { name: "Ana" };
const userNull = { name: "Ana", email: null };
console.log("'email' in user:", "email" in user);
console.log("user.email:", user.email);
console.log("'email' in userNull:", "email" in userNull);
console.log("userNull.email:", userNull.email);
console.log("user.email === undefined:", user.email === undefined);
console.log("userNull.email === null:", userNull.email === null);
```

```
'email' in user: false
user.email: undefined
'email' in userNull: true
userNull.email: null
user.email === undefined: true
userNull.email === null: true
```

APIs often use `null` to mean "field exists but is empty" (no email on file) and omit the key - or leave it `undefined` after `JSON.parse` of a missing field - to mean "not provided". Pick one convention in your own code and stick to it. Object keys with an `undefined` value still show up in `Object.keys` if you assigned them explicitly:

```
const mixed = { a: null, b: undefined, c: 1 };
console.log("Object.keys(mixed):", Object.keys(mixed));
console.log("Object.values(mixed):", Object.values(mixed));
console.log("Object.entries(mixed):", Object.entries(mixed));
```

```
Object.keys(mixed): [ 'a', 'b', 'c' ]
Object.values(mixed): [ null, undefined, 1 ]
Object.entries(mixed): [ [ 'a', null ], [ 'b', undefined ], [ 'c', 1 ] ]
```

## Default parameters: undefined triggers, null does not

This is one of the most practical differences in day-to-day functions:

```
function greet(name = "guest") {
  return "hi " + name;
}
console.log("greet():", greet());
console.log("greet(undefined):", greet(undefined));
console.log("greet(null):", greet(null));
console.log("greet('Ada'):", greet("Ada"));
```

```
greet(): hi guest
greet(undefined): hi guest
greet(null): hi null
greet('Ada'): hi Ada
```

Omitting the argument or passing `undefined` applies the default. Passing `null` does not - you get the string `"null"` after concatenation. If callers might send `null` from an API, normalize at the edge: `name = name ?? "guest"` inside the body, or reject `null` explicitly. Variables declared with `let`/`const` without an initializer are `undefined` until assigned; see [var vs let vs const](https://mfaysal.com/blog/javascript-var-vs-let-vs-const) for the scoping side of that story.

## Optional chaining (?.) safely reads nested values

Optional chaining short-circuits to `undefined` when it hits `null` or `undefined`, instead of throwing:

```
const payload = { user: { id: 7 } };
console.log("payload.user?.profile?.avatar:", payload.user?.profile?.avatar);
console.log("payload.missing?.x:", payload.missing?.x);
const maybeNull = null;
console.log("maybeNull?.length:", maybeNull?.length);
```

```
payload.user?.profile?.avatar: undefined
payload.missing?.x: undefined
maybeNull?.length: undefined
```

That return value is always `undefined` on a miss - never `null` - even if an intermediate was `null`. MDN's page on [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) covers `?.()` and `?.[]` forms too. Pair it with `??` when you want a default after a safe read.

## Nullish coalescing (??) vs logical OR (||)

`??` only falls through for `null` and `undefined`. `||` falls through for every falsy value:

```
console.log("null ?? 'fallback':", null ?? "fallback");
console.log("undefined ?? 'fallback':", undefined ?? "fallback");
console.log("0 ?? 'fallback':", 0 ?? "fallback");
console.log("'' ?? 'fallback':", "" ?? "fallback");
console.log("false ?? 'fallback':", false ?? "fallback");
console.log("null || 'or':", null || "or");
console.log("0 || 'or':", 0 || "or");
```

```
null ?? 'fallback': fallback
undefined ?? 'fallback': fallback
0 ?? 'fallback': 0
'' ?? 'fallback': 
false ?? 'fallback': false
null || 'or': or
0 || 'or': or
```

Use `??` when `0`, `''` or `false` are valid data. Use `||` only when any falsy value should become the default. MDN documents the operator under [nullish coalescing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing).

## JSON.stringify and JSON.parse quirks

JSON has `null` but no `undefined`. Serialization reflects that:

```
console.log("JSON.stringify({a:null,b:undefined}):", JSON.stringify({ a: null, b: undefined }));
console.log("JSON.stringify([null, undefined]):", JSON.stringify([null, undefined]));
console.log("JSON.parse('{\"a\":null}'):", JSON.parse('{"a":null}'));
console.log("JSON.parse('{}').b:", JSON.parse("{}").b);
```

```
JSON.stringify({a:null,b:undefined}): {"a":null}
JSON.stringify([null, undefined]): [null,null]
JSON.parse('{"a":null}'): { a: null }
JSON.parse('{}').b: undefined
```

Object keys whose value is `undefined` are dropped. Array holes / `undefined` slots become `null` in the JSON text. After `JSON.parse`, a missing key reads back as `undefined`, while an explicit JSON `null` stays `null`. MDN's [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) page covers replacer behaviour if you need custom handling.

## Function returns, void 0, and ReferenceError

A function with no `return` yields `undefined`. Returning `null` is a deliberate choice:

```
function noReturn() {}
function returnNull() { return null; }
function returnUndef() { return undefined; }
console.log("noReturn():", noReturn());
console.log("returnNull():", returnNull());
console.log("returnUndef():", returnUndef());
console.log("void 0 === undefined:", void 0 === undefined);
let x;
console.log("let x; x === undefined:", x === undefined);
```

```
noReturn(): undefined
returnNull(): null
returnUndef(): undefined
void 0 === undefined: true
let x; x === undefined: true
```

Reading a variable that was never declared is a `ReferenceError`, not `undefined` - except with `typeof`, which safely returns `"undefined"`:

```
try {
  console.log(notDeclaredAtAll);
} catch (e) {
  console.log("caught:", e.name + ": " + e.message);
}
console.log("typeof notDeclaredAtAll:", typeof notDeclaredAtAll);
```

```
caught: ReferenceError: notDeclaredAtAll is not defined
typeof notDeclaredAtAll: undefined
```

Sparse arrays also produce `undefined` reads for empty slots, but `in` says the index is missing:

```
const arr = [1, , 3];
console.log("arr.length:", arr.length);
console.log("arr[1]:", arr[1]);
console.log("1 in arr:", 1 in arr);
console.log("arr.map(x => x):", arr.map(x => x));
```

```
arr.length: 3
arr[1]: undefined
1 in arr: false
arr.map(x => x): [ 1, <1 empty item>, 3 ]
```

## DOM, APIs and everyday pitfalls

Three places this distinction shows up constantly:

- **DOM lookups** - `document.getElementById('missing')` returns `null`. Reading `.value` on that result throws. Guard with `if (el == null)` before you touch properties.
- **Fetch / JSON APIs** - a missing JSON key becomes `undefined` after `JSON.parse`. An explicit `"email": null` stays `null`. Do not treat them the same if your UI has different copy for "unknown" vs "cleared".
- **Function options objects** - `fn({ timeout: undefined })` still has the key; defaults written as `options.timeout ?? 3000` will replace it, which is usually what you want. Passing `timeout: null` also triggers `??`, so document whether `null` means "no timeout" or "use default".

Optional chaining helps for deep reads, but remember a miss always yields `undefined`, even when the chain hit a `null`. If you need to preserve an explicit `null` from an API as `null` in your UI state, assign it deliberately after the read instead of relying on `?.` alone.

Array holes are another footgun: sparse slots read as `undefined`, yet `in` says the index is missing, and `map` skips the callback for empty slots. Prefer dense arrays (`filter`, `flat`, or explicit `undefined` fills) when you care about every index.

## Practical checks and when to use each

Helpers I keep nearby:

```
function isNullish(v) { return v == null; } // catches null and undefined
function isNull(v) { return v === null; }
function isUndef(v) { return typeof v === "undefined"; }
console.log("isNullish(null):", isNullish(null));
console.log("isNullish(undefined):", isNullish(undefined));
console.log("isNullish(0):", isNullish(0));
console.log("isNull(null):", isNull(null));
console.log("isUndef(undefined):", isUndef(undefined));
console.log("isUndef(null):", isUndef(null));
```

```
isNullish(null): true
isNullish(undefined): true
isNullish(0): false
isNull(null): true
isUndef(undefined): true
isUndef(null): false
```

- **Use `undefined`** for "not set yet": missing optional args, uninitialized locals, absent object keys.
- **Use `null`** for "set, but empty": cleared form fields, API "no value", DOM nodes that do not exist (`document.getElementById` returns `null`).
- **Check with `===`** when the difference matters; **`== null`** when either empty state is fine.
- **Prefer `??` over `||`** for defaults when zero/empty-string are valid.
- **Prefer `?.`** over manual null checks for deep reads.

If you call something that is not a function while handling these values, you will see a different error - covered in [TypeError: X is not a function](https://mfaysal.com/blog/javascript-typeerror-is-not-a-function).

Quick decision tree I use when reviewing a PR:

1. Is the empty state intentional and persistent? Prefer `null`.
2. Is the value simply not provided yet? Prefer leaving it `undefined` / omitted.
3. Do I need a default without wiping `0` or `''`? Use `??`, not `||`.
4. Am I reading a deep path that might be missing? Use `?.`, then `??` for the default.
5. Am I comparing to empty? Prefer `== null` for either, or `===` when the exact flavour matters.

All snippets above were run on Node.js 24.22.1 via `test/js/demo.mjs`. The same results hold in current Chromium and Firefox for the language-level bits (`typeof`, `??`, `?.`, `JSON.stringify`). DOM return values are browser APIs, not Node, but the `null` convention is the same in the HTML Standard.

## Frequently asked questions

### What is the difference between null and undefined in JavaScript?

undefined means a value was never assigned (missing property, missing argument, no return). null is an intentional empty value you assign. They compare equal with == and unequal with ===.

### Why is typeof null equal to "object"?

It is a long-standing language quirk. null is still a primitive, not a real object. Detect it with value === null, not with typeof.

### When should I use == null?

When either null or undefined should take the same branch. value == null is true only for those two; it leaves 0, '' and false alone.

### Do default parameters work with null?

No. Defaults apply when the argument is missing or undefined. Passing null uses null as the value.

### What does ?? do that || does not?

?? falls back only for null and undefined. || falls back for every falsy value, including 0, '' and false.

### How does JSON treat null and undefined?

JSON has null but not undefined. JSON.stringify drops object keys whose value is undefined, and turns undefined array slots into null. JSON.parse of a missing key yields undefined when you read it.

### Is a missing object property null or undefined?

Reading a missing property returns undefined. The key is not in the object. An explicit null means the key exists and was set empty.

### What does document.getElementById return when nothing matches?

It returns null, not undefined. That is a common DOM example of intentional emptiness, so check with === null or == null before you read properties.

*Last checked: 7 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.*

---

*Originally published on [mfaysal.com](https://mfaysal.com/blog/javascript-null-vs-undefined).*

I'm learning cybersecurity on TryHackMe — follow my progress at [tryhackme.com/p/mfaysal.dev](https://tryhackme.com/p/mfaysal.dev).

