JavaScript 55 🧬 null vs undefined + nullish coalescing
let a;
console.log(a);
let b = null;
console.log(b);
console.log(typeof undefined);
console.log(typeof null);
console.log(undefined == null);
console.log(undefined === null);
const value1 = null ?? 'default';
console.log(value1);
const value2 = undefined ?? 'default';
console.log(value2);
const value3 = 0 ?? 'default';
console.log(value3);
const value4 = '' ?? 'default';
console.log(value4);
const value5 = false ?? 'default';
console.log(value5);
const config = {
port: 0,
host: null
};
console.log(config.port ?? 8080);
console.log(config.host ?? 'localhost');
let x = null;
x ??= 'assigned';
console.log(x);
let y = 0;
y ??= 42;
console.log(y);
JavaScript has two “empty” values: null and undefined. They look similar, they behave similarly in many ways, but they mean different things. Then there’s ?? — the nullish coalescing operator — which finally gives you a clean way to provide defaults without accidentally catching 0, '', or false.
Key point: undefined means “not yet assigned.” null means “intentionally empty.” They’re loosely equal (==) but not strictly equal (===). And ?? only falls back when the value is null or undefined — unlike ||, which falls back for any falsy value.
a – null vs undefined
Both values represent “nothing” — but in different senses.
undefined — the default empty:
A variable is undefined when it hasn’t been assigned a value.
let a;
console.log(a);
// [ undefined ]
function greet(name) {
console.log(name);
}
greet();
// [ undefined ]
const obj = {};
console.log(obj.missing);
// [ undefined ]
const arr = [1, 2];
console.log(arr[10]);
// [ undefined ]
undefined appears when:
- A variable is declared but not assigned
- A function parameter isn’t passed
- You access a missing object property
- You access an out-of-bounds array index
- A function returns nothing
null — the intentional empty:
null is a value you assign when you mean “nothing.”
let user = null;
// later...
user = { name: 'Alice' };
null appears when:
- You reset a variable to “no value”
- A function returns
nullto signal “not found” - An API explicitly wants to clear a value
function findUser(id) {
if (id === 1) return { name: 'Alice' };
return null; // explicitly "not found"
}
typeof results — the famous quirk:
console.log(typeof undefined);
// [ 'undefined' ]
console.log(typeof null);
// [ 'object' ] ← historical bug
typeof null === 'object' is a bug from the earliest days of JavaScript that was never fixed — too much code depended on it. Never rely on it to detect null.
Equality — loose vs strict:
console.log(undefined == null);
// [ true ]
console.log(undefined === null);
// [ false ]
Loose equality treats them as the same “empty.” Strict equality keeps them distinct.
When each appears:
| Situation | Value |
|---|---|
| Declared, not assigned | undefined |
| Missing function argument | undefined |
| Missing object property | undefined |
| Out-of-bounds array index | undefined |
Function with no return | undefined |
| Explicit “empty” assignment | null |
| API returning “not found” | null |
| Reset to empty | null |
JSON.parse('null') | null |
The convention:
- Use
undefinedfor “not yet set” — mostly produced by JavaScript itself - Use
nullfor “intentionally empty” — set by your code
let selectedUser = null; // nothing selected yet
let query = undefined; // not yet searched
Checking for either:
// Both null and undefined
if (value == null) { ... }
// Only undefined
if (value === undefined) { ... }
// Only null
if (value === null) { ... }
// Either, strict
if (value === null || value === undefined) { ... }
The idiom value == null is the shortest way to check for either.
JSON and null:
JSON.stringify({ a: null, b: undefined });
// [ '{"a":null}' ]
nullsurvives JSON — becomesnullundefineddisappears — the key is dropped entirely
JSON.parse('{"a":null}');
// [ { a: null } ]
JSON.parse('{"a":undefined}');
// SyntaxError
In objects — different behaviors:
const obj = { a: undefined, b: null };
console.log('a' in obj);
// [ true ]
console.log(Object.keys(obj));
// [ [ 'a', 'b' ] ]
JSON.stringify(obj);
// [ '{"b":null}' ] ← a dropped
undefined and null are both keys, but only null serializes.
In arrays:
const arr = [1, undefined, 3];
console.log(arr.length);
// [ 3 ]
JSON.stringify(arr);
// [ '[1,null,3]' ] ← undefined becomes null in arrays
Default parameters:
function greet(name = 'Guest') {
return `Hello, ${name}`;
}
console.log(greet());
// [ 'Hello, Guest' ]
console.log(greet(undefined));
// [ 'Hello, Guest' ]
console.log(greet(null));
// [ 'Hello, null' ] ← null doesn't trigger default
Default parameters only kick in for undefined — not for null.
Comparison table:
| Feature | undefined | null |
|---|---|---|
| Meaning | Not assigned | Intentionally empty |
| Default for unset | ✅ | ❌ |
typeof | 'undefined' | 'object' |
== null | ✅ | ✅ |
=== null | ❌ | ✅ |
| JSON | Dropped | Preserved |
| Default params | Triggers | Doesn’t trigger |
Number() | NaN | 0 |
String() | 'undefined' | 'null' |
Boolean() | false | false |
b – Nullish coalescing (??)
The ?? operator returns the right side when the left is null or undefined. Otherwise, it returns the left side.
Basic usage:
const a = null ?? 'default';
console.log(a);
// [ 'default' ]
const b = undefined ?? 'default';
console.log(b);
// [ 'default' ]
const c = 'value' ?? 'default';
console.log(c);
// [ 'value' ]
The critical difference from ||:
|| falls back for any falsy value — 0, '', false, NaN, null, undefined.
?? falls back only for null and undefined.
const a = 0 || 'default';
console.log(a);
// [ 'default' ] ← 0 was replaced
const b = 0 ?? 'default';
console.log(b);
// [ 0 ] ← 0 preserved
const c = '' || 'default';
console.log(c);
// [ 'default' ]
const d = '' ?? 'default';
console.log(d);
// [ '' ] ← empty string preserved
const e = false || 'default';
console.log(e);
// [ 'default' ]
const f = false ?? 'default';
console.log(f);
// [ false ] ← false preserved
This is exactly why ?? was introduced — || was eating valid falsy values.
Comparison table:
| Value | x || 'default' | x ?? 'default' |
|---|---|---|
null | 'default' | 'default' |
undefined | 'default' | 'default' |
0 | 'default' | 0 |
'' | 'default' | '' |
false | 'default' | false |
NaN | 'default' | NaN |
'value' | 'value' | 'value' |
Chaining:
const port = process.env.PORT ?? 3000;
const host = process.env.HOST ?? 'localhost';
With object properties:
const config = {
port: 0,
host: null,
name: ''
};
console.log(config.port ?? 8080);
// [ 0 ]
console.log(config.host ?? 'localhost');
// [ 'localhost' ]
console.log(config.name ?? 'app');
// [ '' ]
With function calls:
function getConfig() {
return null;
}
const value = getConfig() ?? 'fallback';
console.log(value);
// [ 'fallback' ]
With optional chaining (?.):
The two are natural partners:
const user = {
profile: null
};
const city = user.profile?.address?.city ?? 'Unknown';
console.log(city);
// [ 'Unknown' ]
?. short-circuits if any step is null/undefined and returns undefined. Then ?? provides the fallback.
Nullish assignment (??=):
??= assigns only if the left side is null or undefined:
let x = null;
x ??= 'assigned';
console.log(x);
// [ 'assigned' ]
let y = 0;
y ??= 42;
console.log(y);
// [ 0 ] ← already set, not overwritten
Comparison of assignment operators:
| Operator | Assigns when |
|---|---|
= | Always |
||= | Left is falsy |
&&= | Left is truthy |
??= | Left is null/undefined |
let a = 0;
a ||= 5;
console.log(a);
// [ 5 ] ← 0 was falsy
let b = 0;
b ??= 5;
console.log(b);
// [ 0 ] ← 0 not nullish
let c = null;
c ??= 5;
console.log(c);
// [ 5 ]
Operator precedence:
?? has low precedence. It can’t be mixed with || or && without parentheses:
a || b ?? c;
// SyntaxError: Cannot mix ?? and || without parentheses
(a || b) ?? c;
// ✅ works
a ?? (b || c);
// ✅ works
You must wrap one side in parentheses to combine them.
The practical use case:
function configure(options) {
const port = options.port ?? 8080;
const timeout = options.timeout ?? 30000;
const retries = options.retries ?? 3;
const debug = options.debug ?? false;
return { port, timeout, retries, debug };
}
console.log(configure({ port: 0, debug: false }));
// [ { port: 0, timeout: 30000, retries: 3, debug: false } ]
Every value is preserved exactly as given — no accidental overwrites from ||.
Common pitfalls:
// ❌ Using || when you want ?? semantics
const port = process.env.PORT || 8080; // 0 becomes 8080
const port = process.env.PORT ?? 8080; // 0 stays 0
// ❌ Mixing without parentheses
const x = a ?? b || c; // SyntaxError
// ❌ Expecting ?? to work on false
const d = false ?? 'default'; // false
const e = false || 'default'; // 'default'
// ✅ Use parentheses when combining
const x = (a || b) ?? c;
When to use ?? vs ||:
| Situation | Use |
|---|---|
| Fallback for null/undefined only | ?? |
| Fallback for any falsy | || |
| Config with 0/”/false valid | ?? |
| Boolean flag defaulting | || |
| Numeric default where 0 matters | ?? |
| String default where ” matters | ?? |
c – Common patterns
These patterns come up constantly in modern JavaScript.
Pattern 1 — Config defaults with 0-safe fallbacks:
function setup(options = {}) {
return {
port: options.port ?? 8080,
host: options.host ?? 'localhost',
debug: options.debug ?? false
};
}
console.log(setup({ port: 0 }));
// [ { port: 0, host: 'localhost', debug: false } ]
Pattern 2 — Combining ?. and ??:
const user = {
settings: {
theme: null
}
};
const theme = user.settings?.theme ?? 'light';
console.log(theme);
// [ 'light' ]
Pattern 3 — Function defaults for null/undefined:
function greet(name) {
const greeting = name ?? 'stranger';
return `Hello, ${greeting}`;
}
console.log(greet());
// [ 'Hello, stranger' ]
console.log(greet('Alice'));
// [ 'Hello, Alice' ]
Pattern 4 — Safe array access:
function getFirst(arr) {
return arr?.[0] ?? 'empty';
}
console.log(getFirst([1, 2, 3]));
// [ 1 ]
console.log(getFirst([]));
// [ 'empty' ]
console.log(getFirst());
// [ 'empty' ]
Pattern 5 — Environment variables:
const PORT = Number(process.env.PORT ?? 3000);
const HOST = process.env.HOST ?? '0.0.0.0';
const DEBUG = process.env.DEBUG === 'true';
Pattern 6 — API response fallbacks:
function getUserName(response) {
return response?.data?.user?.name ?? 'Anonymous';
}
Pattern 7 — Cache pattern:
const cache = new Map();
function getValue(key) {
return cache.get(key) ?? computeValue(key);
}
Pattern 8 — Lazy initialization:
let instance = null;
function getInstance() {
instance ??= createInstance();
return instance;
}
Pattern 9 — Optional options object:
function render(element, options) {
const { width = 100, height = 100 } = options ?? {};
// ...
}
Note: options ?? {} protects against a null options object.
Pattern 10 — Safe property access with defaults:
const config = {
server: null
};
const port = config.server?.port ?? 8080;
console.log(port);
// [ 8080 ]
Pattern 11 — Form input defaults:
function getFormValue(form, field) {
return form?.[field] ?? '';
}
Pattern 12 — Nullish vs logical — the choice:
// If zero is a valid value
function setVolume(v) {
const volume = v ?? 50;
return volume;
}
setVolume(0); // 0 (correct)
setVolume(); // 50
setVolume(null); // 50
Pattern 13 — Defaults with destructuring:
function process({ name, age } = {}) {
const userName = name ?? 'Guest';
const userAge = age ?? 0;
return { userName, userAge };
}
console.log(process({ name: 'Alice' }));
// [ { userName: 'Alice', userAge: 0 } ]
console.log(process());
// [ { userName: 'Guest', userAge: 0 } ]
Pattern 14 — Chained fallbacks:
function getConfig() {
return process.env.CONFIG ?? loadFromFile() ?? getDefaults();
}
Pattern 15 — Nullish in options merging:
const defaults = { retries: 3, timeout: 5000 };
function withDefaults(opts) {
return {
retries: opts.retries ?? defaults.retries,
timeout: opts.timeout ?? defaults.timeout
};
}
console.log(withDefaults({ retries: 0 }));
// [ { retries: 0, timeout: 5000 } ]
Pattern 16 — Map lookup with fallback:
const icons = new Map([['home', '🏠'], ['user', '👤']]);
function getIcon(key) {
return icons.get(key) ?? '❓';
}
console.log(getIcon('home'));
// [ '🏠' ]
console.log(getIcon('missing'));
// [ '❓' ]
Pattern 17 — Nullish and array destructuring:
function getCoords(point) {
const [x, y] = point ?? [0, 0];
return { x, y };
}
console.log(getCoords([10, 20]));
// [ { x: 10, y: 20 } ]
console.log(getCoords());
// [ { x: 0, y: 0 } ]
Pattern 18 — Nullish for numbers:
function addTax(price, rate) {
const taxRate = rate ?? 0.1;
return price * (1 + taxRate);
}
console.log(addTax(100, 0));
// [ 100 ] ← 0% tax preserved
console.log(addTax(100));
// [ 110 ]
Pattern 19 — Combining typeof and ??:
function safeParse(json) {
try {
return JSON.parse(json);
} catch {
return null;
}
}
const data = safeParse('invalid') ?? { default: true };
console.log(data);
// [ { default: true } ]
Pattern 20 — Full example:
function createServer(options = {}) {
return {
host: options.host ?? '0.0.0.0',
port: options.port ?? 3000,
timeout: options.timeout ?? 30000,
retries: options.retries ?? 3,
debug: options.debug ?? false,
logLevel: options.logLevel ?? 'info'
};
}
console.log(createServer({ port: 0, debug: false, retries: 0 }));
// [ { host: '0.0.0.0', port: 0, timeout: 30000, retries: 0, debug: false, logLevel: 'info' } ]
Every falsy-but-valid value preserved.
Complete Example Session
// ============================================
// PART 1: UNDEFINED
// ============================================
let a;
console.log(a);
// [ undefined ]
function fn(x) { return x; }
console.log(fn());
// [ undefined ]
const obj1 = {};
console.log(obj1.missing);
// [ undefined ]
// ============================================
// PART 2: NULL
// ============================================
let b = null;
console.log(b);
// [ null ]
function findUser(id) {
return id === 1 ? { name: 'Alice' } : null;
}
console.log(findUser(99));
// [ null ]
// ============================================
// PART 3: TYPEOF
// ============================================
console.log(typeof undefined);
// [ 'undefined' ]
console.log(typeof null);
// [ 'object' ] ← historical bug
// ============================================
// PART 4: EQUALITY
// ============================================
console.log(undefined == null);
// [ true ]
console.log(undefined === null);
// [ false ]
// ============================================
// PART 5: JSON
// ============================================
console.log(JSON.stringify({ a: null, b: undefined }));
// [ '{"a":null}' ]
console.log(JSON.stringify([1, undefined, 3]));
// [ '[1,null,3]' ]
// ============================================
// PART 6: DEFAULT PARAMETERS
// ============================================
function greet(name = 'Guest') {
return `Hello, ${name}`;
}
console.log(greet());
// [ 'Hello, Guest' ]
console.log(greet(undefined));
// [ 'Hello, Guest' ]
console.log(greet(null));
// [ 'Hello, null' ]
// ============================================
// PART 7: NULLISH COALESCING
// ============================================
console.log(null ?? 'default');
// [ 'default' ]
console.log(undefined ?? 'default');
// [ 'default' ]
console.log(0 ?? 'default');
// [ 0 ]
console.log('' ?? 'default');
// [ '' ]
console.log(false ?? 'default');
// [ false ]
// ============================================
// PART 8: VS LOGICAL OR
// ============================================
console.log(0 || 'default');
// [ 'default' ]
console.log(0 ?? 'default');
// [ 0 ]
console.log('' || 'default');
// [ 'default' ]
console.log('' ?? 'default');
// [ '' ]
console.log(false || 'default');
// [ 'default' ]
console.log(false ?? 'default');
// [ false ]
// ============================================
// PART 9: CONFIG EXAMPLE
// ============================================
const config = {
port: 0,
host: null
};
console.log(config.port ?? 8080);
// [ 0 ]
console.log(config.host ?? 'localhost');
// [ 'localhost' ]
// ============================================
// PART 10: NULLISH ASSIGNMENT
// ============================================
let x = null;
x ??= 'assigned';
console.log(x);
// [ 'assigned' ]
let y = 0;
y ??= 42;
console.log(y);
// [ 0 ]
// ============================================
// PART 11: COMBINED WITH OPTIONAL CHAINING
// ============================================
const user = { profile: null };
const city = user.profile?.address?.city ?? 'Unknown';
console.log(city);
// [ 'Unknown' ]
// ============================================
// PART 12: PARENTHESES REQUIRED
// ============================================
try {
eval('null ?? 1 || 2');
} catch (err) {
console.log(err.message);
}
// [ Unexpected token '||' ]
// ============================================
// PART 13: SAFE ARRAY ACCESS
// ============================================
function first(arr) {
return arr?.[0] ?? 'empty';
}
console.log(first([1, 2]));
// [ 1 ]
console.log(first());
// [ 'empty' ]
// ============================================
// PART 14: CONFIG DEFAULTS
// ============================================
function setup(options = {}) {
return {
port: options.port ?? 8080,
debug: options.debug ?? false,
retries: options.retries ?? 3
};
}
console.log(setup({ port: 0, debug: false }));
// [ { port: 0, debug: false, retries: 3 } ]
// ============================================
// PART 15: LAZY INIT
// ============================================
let instance = null;
function getInstance() {
instance ??= { id: Math.random() };
return instance;
}
console.log(getInstance() === getInstance());
// [ true ]
// ============================================
// PART 16: CHAINED NULLISH
// ============================================
function getConfig() {
return null;
}
const value = getConfig() ?? 'fallback';
console.log(value);
// [ 'fallback' ]
// ============================================
// PART 17: MAP LOOKUP
// ============================================
const icons = new Map([['home', '🏠']]);
console.log(icons.get('home') ?? '❓');
// [ '🏠' ]
console.log(icons.get('missing') ?? '❓');
// [ '❓' ]
// ============================================
// PART 18: NULLISH VS LOGICAL ASSIGNMENT
// ============================================
let p = 0;
p ||= 5;
console.log(p);
// [ 5 ]
let q = 0;
q ??= 5;
console.log(q);
// [ 0 ]
// ============================================
// PART 19: FORM DEFAULTS
// ============================================
function getField(form, key) {
return form?.[key] ?? '';
}
console.log(getField({ name: 'Alice' }, 'name'));
// [ 'Alice' ]
console.log(getField({}, 'name'));
// [ '' ]
// ============================================
// PART 20: FULL SCRIPT
// ============================================
let a55;
console.log(a55);
let b55 = null;
console.log(b55);
console.log(typeof undefined);
console.log(typeof null);
console.log(undefined == null);
console.log(undefined === null);
const value1_55 = null ?? 'default';
console.log(value1_55);
const value2_55 = undefined ?? 'default';
console.log(value2_55);
const value3_55 = 0 ?? 'default';
console.log(value3_55);
const value4_55 = '' ?? 'default';
console.log(value4_55);
const value5_55 = false ?? 'default';
console.log(value5_55);
const config55 = {
port: 0,
host: null
};
console.log(config55.port ?? 8080);
console.log(config55.host ?? 'localhost');
let x55 = null;
x55 ??= 'assigned';
console.log(x55);
let y55 = 0;
y55 ??= 42;
console.log(y55);
Quick Reference
null vs undefined
| Aspect | undefined | null |
|---|---|---|
| Meaning | Not assigned | Intentionally empty |
| Default for unset | ✅ | ❌ |
typeof | 'undefined' | 'object' |
== null | ✅ | ✅ |
=== null | ❌ | ✅ |
Number() | NaN | 0 |
String() | 'undefined' | 'null' |
Boolean() | false | false |
| JSON | Dropped | Preserved |
| Default params | Triggers | Doesn’t trigger |
Nullish Coalescing
| Expression | Result |
|---|---|
null ?? 'x' | 'x' |
undefined ?? 'x' | 'x' |
0 ?? 'x' | 0 |
'' ?? 'x' | '' |
false ?? 'x' | false |
NaN ?? 'x' | NaN |
'v' ?? 'x' | 'v' |
?? vs ||
| Value | || | ?? |
|---|---|---|
null | fallback | fallback |
undefined | fallback | fallback |
0 | fallback | keep |
'' | fallback | keep |
false | fallback | keep |
NaN | fallback | keep |
Logical Assignment
| Operator | Assigns when |
|---|---|
= | Always |
||= | Falsy |
&&= | Truthy |
??= | Null or undefined |
Checking for “empty”
| Check | Meaning |
|---|---|
x === undefined | Only undefined |
x === null | Only null |
x == null | Either |
x === null || x === undefined | Either (strict) |
Common Idioms
| Idiom | Purpose |
|---|---|
value ?? default | Null-safe default |
obj?.prop ?? fallback | Safe access + default |
x ??= value | Assign if nullish |
options.port ?? 8080 | Config with 0 preserved |
data ?? {} | Guard against null |
Gotchas
| Issue | Solution |
|---|---|
typeof null === 'object' | Use === null |
NaN ?? 'x' returns NaN | Use Number.isNaN first |
?? with || without parens | SyntaxError |
JSON drops undefined | Use null |
Default params skip null | Handle explicitly |
Best Practices
✅ Do This:
// Use ?? for null-safe defaults
const port = options.port ?? 8080; // ✅
// Use == null to check either
if (value == null) { ... } // ✅
// Use === null for strict null
if (value === null) { ... } // ✅
// Combine with optional chaining
const city = user?.address?.city ?? 'Unknown'; // ✅
// Use ??= for lazy init
instance ??= createInstance(); // ✅
// Preserve 0 and ''
const count = data.count ?? 0; // ✅
const name = data.name ?? ''; // ✅
// Default params for undefined only
function f(x = 10) { ... } // ✅
// Use null for "not found"
return null; // ✅
❌ Don’t Do This:
// Don't use || when 0/false/'' are valid
const port = options.port || 8080; // ❌ 0 becomes 8080
const port = options.port ?? 8080; // ✅
// Don't rely on typeof null
typeof null === 'object'; // ❌ misleading
value === null; // ✅
// Don't mix ?? with || without parens
a ?? b || c; // ❌ SyntaxError
(a ?? b) || c; // ✅
// Don't check for undefined with truthiness
if (value) { ... } // ❌ fails on 0, '', false
if (value != null) { ... } // ✅
// Don't use default params for null
function f(x = 10) { ... }
f(null); // ⚠️ x = null
function f(x) { x = x ?? 10; ... } // ✅
// Don't forget ?? is not ||-compatible
const x = a ?? b ?? c; // ✅ chained
const x = a || b ?? c; // ❌ SyntaxError
// Don't assume JSON preserves undefined
JSON.stringify({ a: undefined }); // ❌ '{}'
JSON.stringify({ a: null }); // ✅ '{"a":null}'
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
|| eats 0 | Wrong default | Use ?? |
typeof null | 'object' | Check === null |
== null vs === null | Loose vs strict | Use what fits |
JSON drops undefined | Missing keys | Use null |
Default param skips null | Wrong value | Use ?? inside |
Mixing ?? and || | SyntaxError | Add parentheses |
NaN ?? x | Returns NaN | Check NaN first |
| Optional chaining on missing root | undefined | Use ?? fallback |
Real-World Examples
1. Declared, Unassigned
let a;
console.log(a);
// [ undefined ]
2. Explicit Null
let b = null;
console.log(b);
// [ null ]
3. typeof
console.log(typeof undefined);
// [ 'undefined' ]
console.log(typeof null);
// [ 'object' ]
4. Loose Equality
console.log(undefined == null);
// [ true ]
5. Strict Equality
console.log(undefined === null);
// [ false ]
6. Nullish Default
console.log(null ?? 'default');
// [ 'default' ]
7. Zero Preserved
console.log(0 ?? 'default');
// [ 0 ]
8. Empty String Preserved
console.log('' ?? 'default');
// [ '' ]
9. False Preserved
console.log(false ?? 'default');
// [ false ]
10. Config with Zero
const config = { port: 0 };
console.log(config.port ?? 8080);
// [ 0 ]
11. Config with Null
const config = { host: null };
console.log(config.host ?? 'localhost');
// [ 'localhost' ]
12. Nullish Assignment
let x = null;
x ??= 'assigned';
console.log(x);
// [ 'assigned' ]
13. No Overwrite
let y = 0;
y ??= 42;
console.log(y);
// [ 0 ]
14. With Optional Chaining
const user = { profile: null };
console.log(user.profile?.city ?? 'Unknown');
// [ 'Unknown' ]
15. JSON
console.log(JSON.stringify({ a: null, b: undefined }));
// [ '{"a":null}' ]
16. Default Params vs Null
function f(x = 10) { return x; }
console.log(f());
// [ 10 ]
console.log(f(undefined));
// [ 10 ]
console.log(f(null));
// [ null ]
17. Logical OR vs Nullish
console.log(0 || 'x');
// [ 'x' ]
console.log(0 ?? 'x');
// [ 0 ]
18. Safe Array Access
function first(arr) {
return arr?.[0] ?? 'empty';
}
console.log(first([1, 2]));
// [ 1 ]
console.log(first());
// [ 'empty' ]
19. Lazy Init
let instance = null;
function getInstance() {
instance ??= { id: 1 };
return instance;
}
console.log(getInstance() === getInstance());
// [ true ]
20. Full Script
let a55;
console.log(a55);
let b55 = null;
console.log(b55);
console.log(typeof undefined);
console.log(typeof null);
console.log(undefined == null);
console.log(undefined === null);
const value1_55 = null ?? 'default';
console.log(value1_55);
const value2_55 = undefined ?? 'default';
console.log(value2_55);
const value3_55 = 0 ?? 'default';
console.log(value3_55);
const value4_55 = '' ?? 'default';
console.log(value4_55);
const value5_55 = false ?? 'default';
console.log(value5_55);
const config55 = {
port: 0,
host: null
};
console.log(config55.port ?? 8080);
console.log(config55.host ?? 'localhost');
let x55 = null;
x55 ??= 'assigned';
console.log(x55);
let y55 = 0;
y55 ??= 42;
console.log(y55);
Visual: null vs undefined
┌──────────────────────────────────────────────┐
│ undefined │
│ │
│ let x; → undefined │
│ fn() → undefined │
│ obj.missing → undefined │
│ arr[999] → undefined │
│ │
│ JavaScript's default "not set" │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ null │
│ │
│ let x = null; → null │
│ findUser(99) → null │
│ reset() → null │
│ │
│ Explicit "intentionally empty" │
│ │
└──────────────────────────────────────────────┘
Visual: ?? vs ||
┌──────────────────────────────────────────────┐
│ value ──► ?? 'default' │
│ │
│ null → 'default' │
│ undefined → 'default' │
│ 0 → 0 │
│ '' → '' │
│ false → false │
│ NaN → NaN │
│ 'x' → 'x' │
│ │
│ Only null/undefined fall back │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ value ──► || 'default' │
│ │
│ null → 'default' │
│ undefined → 'default' │
│ 0 → 'default' ← ❌ lost │
│ '' → 'default' ← ❌ lost │
│ false → 'default' ← ❌ lost │
│ NaN → 'default' ← ❌ lost │
│ 'x' → 'x' │
│ │
│ Any falsy falls back │
│ │
└──────────────────────────────────────────────┘
Visual: Nullish Assignment
┌──────────────────────────────────────────────┐
│ x ??= value │
│ │
│ if (x == null) x = value │
│ │
│ x = null → assigned │
│ x = undefined → assigned │
│ x = 0 → untouched │
│ x = '' → untouched │
│ x = false → untouched │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Meaning | Example |
|---|---|---|
undefined | Not assigned | let x; |
null | Intentionally empty | let x = null; |
typeof null | 'object' (bug) | Historical |
== null | Both | Either check |
=== null | Only null | Strict |
?? | Nullish default | x ?? 'default' |
|| | Falsy default | x || 'default' |
??= | Assign if nullish | x ??= 5 |
?. | Optional chaining | a?.b |
| JSON null | Preserved | {"a":null} |
| JSON undefined | Dropped | {} |
Key takeaways:
undefinedmeans “not assigned” — JavaScript’s default for emptynullmeans “intentionally empty” — you assign ittypeof null === 'object'is a historical bug — never rely on itundefined == nullis true;undefined === nullis false??falls back only fornullandundefined— perfect for config defaults||falls back for any falsy value — dangerous for0,'',false??=assigns only if the current value is nullish- Use
?.with??for safe property access with fallbacks - JSON preserves
null, dropsundefined - Default parameters trigger for
undefinedbut not fornull - You must parenthesize when mixing
??with||or&& - Choose
nullwhen you mean “no value,”undefinedwhen you mean “not yet set”
Remember: null and undefined both mean “empty,” but they mean different kinds of empty — intentional versus unassigned. ?? is the modern default operator that respects 0, '', and false, while || treats them all as empty. Use == null to check for either, === null for strict null, and ??= for lazy defaults. Master the difference, and your code stops silently eating valid falsy values.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!