JavaScript Intermediate #5 Optional Chaining and Nullish Coalescing
Following #4 Destructuring/spread in depth, this post covers two short but powerful ES2020 operators — optional chaining (?.) and nullish coalescing (??).
?. — optional chaining
#
The operator for safely accessing deep objects.
// old
const street = user && user.address && user.address.street;
// modern
const street = user?.address?.street;Half the length for the same job. If user is null/undefined, it stops there and returns undefined. It doesn’t continue.
How it works #
?. in one line:
If the left value is
nullorundefined, stop and returnundefined. Otherwise, evaluate normally.
const obj = null;
obj?.foo; // undefined (stops because of null)
obj?.foo.bar; // undefined (stopped — .bar isn't evaluated)
const obj2 = { foo: { bar: 42 } };
obj2?.foo?.bar; // 42 (all meaningful values)
obj2?.unknown?.x; // undefined
If any link is null/undefined, stop there — result is undefined.
Function call — ?.()
#
Safely call a method/callback that may not exist.
const callback = options.onSuccess;
callback?.();
obj.greet?.();If callback is defined, call it; otherwise nothing happens. Common in functions that take callback options.
Array/brackets — ?.[]
#
const items = config?.items;
const first = config?.items?.[0];
const dynamic = obj?.[someKey];You can’t append brackets directly to ?. — write ?.[] or ?.[key].
Pitfall of ?. — don’t use everywhere
#
Convenient as it is, peppering optional chaining everywhere hides bugs.
function processUser(user) {
return user?.id?.toUpperCase(); // ✗ overly defensive
}If user is genuinely optional, fine. But if user should always be passed, ?. silently swallows the bug. A null user could be the real problem.
A good guideline: use ?. only where things really might be missing. Don’t sprinkle it “just in case.”
?? — nullish coalescing
#
The operator that gives a default value when something is null or undefined.
const name = user.name ?? 'anonymous';
const age = user.age ?? 0;If the left value is null/undefined, take the right; otherwise keep the left.
?? vs || — falsy vs nullish
#
In the past, || did the same job. The difference is subtle but important.
const a = 0 || 10; // 10 (0 is falsy)
const b = 0 ?? 10; // 0 (0 isn't nullish)
const c = '' || 'default'; // 'default'
const d = '' ?? 'default'; // ''
const e = false || true; // true
const f = false ?? true; // false
|| triggers on all 7 falsy values (false/0/-0/0n/’’/null/undefined/NaN). ?? triggers on exactly two — null/undefined.
This matters because 0, '', false, and other meaningful values are falsy, which caused frequent bugs.
function withTimeout(timeout) {
const ms = timeout || 5000; // ✗ blocks timeout=0
// ...
}
withTimeout(0); // ms = 5000, 0 is ignored
function withTimeout(timeout) {
const ms = timeout ?? 5000;
// ...
}
withTimeout(0); // ms = 0
For default-value patterns, almost always ?? matches your intent. Use || only when “if empty or zero, use default” really is the intent.
When the two meet — ?. + ??
#
Often paired.
const username = response?.user?.name ?? 'anonymous';
// | |
// | default for the empty slot
// deep-safe access
const port = config?.server?.port ?? 3000;Safely access a deep place and default if missing — solved in one line. One of modern JavaScript’s most common idioms.
??= and friends — logical assignment operators
#
A short form added in ES2021.
let user = { name: 'Curtis' };
user.email ??= 'default@example.com';
// assigns if user.email is null/undefined; otherwise leaves it
There are three variants.
a ||= b; // if a is falsy, a = b
a ??= b; // if a is nullish, a = b
a &&= b; // if a is truthy, a = b
||= vs ??= is the same difference as before — falsy vs nullish.
Pitfall — operator precedence #
When mixing ?? with ||/&&, parentheses are required. JavaScript catches it at compile time.
const a = null ?? true && false; // ✗ SyntaxError
const b = (null ?? true) && false; // OK — false
const c = null ?? (true && false); // OK — false
The language blocks ambiguous expressions. Reduces mistakes.
Optional chaining and destructuring #
const { user: { name } = {} } = response ?? {};Looks complex but two steps:
response ?? {}— start with an empty object if response is null/undefineduser: { name } = {}— start with an empty object when user is missing (so name = undefined)
If this gets too tight, splitting it out is better.
const user = response?.user;
const name = user?.name ?? 'anonymous';Optional chaining vs simple checks — which is better? #
// optional chaining
if (user?.address?.street) {
// ...
}
// explicit check
if (user && user.address) {
// ...
}In most cases ?. is shorter and more intuitive. But when the check itself is the intent, an explicit check expresses intent better. Largely a convention difference — follow team style.
Wrap-up #
What we covered:
?.— stops and returns undefined when the left is null/undefined- Use
?.(),?.[]for calls / array access - Don’t sprinkle
?.everywhere — only where things are truly optional ??— triggers only on null/undefined??vs||— preserves meaningful values like 0, ‘’, false- Default-value patterns almost always want
?? ??=,||=,&&=— logical assignment operators- Parentheses required when mixing
??with||/&&
In the next post (#6 fetch API and Error Handling) we cover the standard network tool that works in both browser and Node — fetch usage, error handling, and AbortController.