| |

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 null to 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:

SituationValue
Declared, not assignedundefined
Missing function argumentundefined
Missing object propertyundefined
Out-of-bounds array indexundefined
Function with no returnundefined
Explicit “empty” assignmentnull
API returning “not found”null
Reset to emptynull
JSON.parse('null')null

The convention:

  • Use undefined for “not yet set” — mostly produced by JavaScript itself
  • Use null for “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}' ]
  • null survives JSON — becomes null
  • undefined disappears — 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:

Featureundefinednull
MeaningNot assignedIntentionally empty
Default for unset
typeof'undefined''object'
== null
=== null
JSONDroppedPreserved
Default paramsTriggersDoesn’t trigger
Number()NaN0
String()'undefined''null'
Boolean()falsefalse

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 value0, '', 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:

Valuex || '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:

OperatorAssigns 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 ||:

SituationUse
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

Aspectundefinednull
MeaningNot assignedIntentionally empty
Default for unset
typeof'undefined''object'
== null
=== null
Number()NaN0
String()'undefined''null'
Boolean()falsefalse
JSONDroppedPreserved
Default paramsTriggersDoesn’t trigger

Nullish Coalescing

ExpressionResult
null ?? 'x''x'
undefined ?? 'x''x'
0 ?? 'x'0
'' ?? 'x'''
false ?? 'x'false
NaN ?? 'x'NaN
'v' ?? 'x''v'

?? vs ||

Value||??
nullfallbackfallback
undefinedfallbackfallback
0fallbackkeep
''fallbackkeep
falsefallbackkeep
NaNfallbackkeep

Logical Assignment

OperatorAssigns when
=Always
||=Falsy
&&=Truthy
??=Null or undefined

Checking for “empty”

CheckMeaning
x === undefinedOnly undefined
x === nullOnly null
x == nullEither
x === null || x === undefinedEither (strict)

Common Idioms

IdiomPurpose
value ?? defaultNull-safe default
obj?.prop ?? fallbackSafe access + default
x ??= valueAssign if nullish
options.port ?? 8080Config with 0 preserved
data ?? {}Guard against null

Gotchas

IssueSolution
typeof null === 'object'Use === null
NaN ?? 'x' returns NaNUse Number.isNaN first
?? with || without parensSyntaxError
JSON drops undefinedUse null
Default params skip nullHandle 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

PitfallProblemSolution
|| eats 0Wrong defaultUse ??
typeof null'object'Check === null
== null vs === nullLoose vs strictUse what fits
JSON drops undefinedMissing keysUse null
Default param skips nullWrong valueUse ?? inside
Mixing ?? and ||SyntaxErrorAdd parentheses
NaN ?? xReturns NaNCheck NaN first
Optional chaining on missing rootundefinedUse ?? 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

ConceptMeaningExample
undefinedNot assignedlet x;
nullIntentionally emptylet x = null;
typeof null'object' (bug)Historical
== nullBothEither check
=== nullOnly nullStrict
??Nullish defaultx ?? 'default'
||Falsy defaultx || 'default'
??=Assign if nullishx ??= 5
?.Optional chaininga?.b
JSON nullPreserved{"a":null}
JSON undefinedDropped{}

Key takeaways:

  • undefined means “not assigned” — JavaScript’s default for empty
  • null means “intentionally empty” — you assign it
  • typeof null === 'object' is a historical bug — never rely on it
  • undefined == null is true; undefined === null is false
  • ?? falls back only for null and undefined — perfect for config defaults
  • || falls back for any falsy value — dangerous for 0, '', false
  • ??= assigns only if the current value is nullish
  • Use ?. with ?? for safe property access with fallbacks
  • JSON preserves null, drops undefined
  • Default parameters trigger for undefined but not for null
  • You must parenthesize when mixing ?? with || or &&
  • Choose null when you mean “no value,” undefined when 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!