Optional Chaining (?.) & Nullish Coalescing (??)
Safer Access, Smarter Defaults
LinkedIn Hook
Two operators that fix two of JavaScript's oldest papercuts — and most developers still use the wrong ones.
?.(optional chaining) lets you reach through nested objects without a defensive wall of&&checks. If any step isnullorundefined, the whole expression short-circuits toundefinedinstead of throwingCannot read property 'x' of undefined. It also works on method calls (obj.fn?.()) and computed access (arr?.[0]).
??(nullish coalescing) is the default-value operator you actually wanted. Unlike||, it only falls back fornullandundefined— so0,'', andfalsekeep flowing through as the real values they are. A port of0means "auto-assign", not "missing".Mix them up and you get the classic production bug:
port || 3000silently overrides a valid0. Useport ?? 3000and the logic is finally correct.In this lesson you'll see the falsy-vs-nullish distinction side by side, the method/array forms of
?., and the elegant combined patternuser?.profile?.name ?? "Anonymous".Read the full lesson -> [link]
#JavaScript #OptionalChaining #NullishCoalescing #ES2020 #InterviewPrep #Frontend #CodingInterview
What You'll Learn
- How
?.safely navigates nested objects, method calls, and computed access without throwing - The precise difference between
??and||, and which falsy values break the||pattern - How to combine
?.and??for safe-access-with-default in one concise expression
Two Operators, Two Old Papercuts
These two operators solve two of JavaScript's most annoying problems: accessing deeply nested properties safely, and providing default values correctly.
Optional Chaining (?.) — Safe Property Access
const user = {
name: "Rakibul",
address: {
city: "Dhaka"
}
};
// Without optional chaining — verbose and fragile
const zip = user && user.address && user.address.zip;
// With optional chaining — clean and safe
const zip2 = user?.address?.zip;
console.log(zip2); // undefined (no error!)
// Without it, accessing deeply nested undefined throws
// user.contact.email // TypeError: Cannot read property 'email' of undefined
const email = user?.contact?.email;
console.log(email); // undefined (safe!)
Optional Chaining with Methods and Arrays
const obj = {
greet() { return "Hello!"; },
items: [1, 2, 3]
};
// Method call — ?.()
console.log(obj.greet?.()); // "Hello!"
console.log(obj.missing?.()); // undefined (doesn't throw!)
// Array/bracket access — ?.[]
console.log(obj.items?.[0]); // 1
console.log(obj.data?.[0]); // undefined
// Chained
const users = null;
console.log(users?.[0]?.name?.toUpperCase()); // undefined
?. vs && — They're NOT the Same
const obj = { count: 0, name: "", active: false };
// && stops at any falsy value
console.log(obj.count && obj.count.toFixed(2)); // 0 (stopped at falsy 0!)
console.log(obj.name && obj.name.toUpperCase()); // "" (stopped at falsy ""!)
console.log(obj.active && obj.active.toString()); // false (stopped at falsy!)
// ?. only stops at null/undefined
console.log(obj.count?.toFixed(2)); // "0.00" (works!)
console.log(obj.name?.toUpperCase()); // "" (works!)
console.log(obj.active?.toString()); // "false" (works!)
Nullish Coalescing (??) — Default Values Done Right
// The problem with ||
const port = 0;
const timeout = "";
console.log(port || 3000); // 3000 — WRONG! 0 is a valid port
console.log(timeout || "5s"); // "5s" — WRONG! "" might be intentional
// ?? only falls back for null/undefined, NOT other falsy values
console.log(port ?? 3000); // 0 — CORRECT!
console.log(timeout ?? "5s"); // "" — CORRECT!
console.log(null ?? "default"); // "default"
console.log(undefined ?? "default"); // "default"
console.log(0 ?? "default"); // 0
console.log("" ?? "default"); // ""
console.log(false ?? "default"); // false
?? vs || — The Full Picture
const values = [0, "", false, null, undefined, NaN];
values.forEach(val => {
console.log(`${String(val).padEnd(12)} || "fb" = ${val || "fb"}`);
console.log(`${String(val).padEnd(12)} ?? "fb" = ${val ?? "fb"}`);
console.log("---");
});
// 0 || "fb" = fb ?? "fb" = 0
// "" || "fb" = fb ?? "fb" = ""
// false || "fb" = fb ?? "fb" = false
// null || "fb" = fb ?? "fb" = fb
// undefined || "fb" = fb ?? "fb" = fb
// NaN || "fb" = fb ?? "fb" = NaN
Key difference: || treats ALL falsy values (0, "", false, null, undefined, NaN) as "missing". ?? treats ONLY null and undefined as "missing".
Combining ?. and ??
const config = {
server: {
port: 0 // intentionally 0 for auto-assign
}
};
// Get port, default to 3000 only if null/undefined
const port = config?.server?.port ?? 3000;
console.log(port); // 0 — correct! (0 is not null/undefined)
// Without ??
const wrongPort = config?.server?.port || 3000;
console.log(wrongPort); // 3000 — wrong! (0 is falsy)
Common Mistakes
- Using
||to supply defaults for configuration values — falsy but valid values like0,"", orfalseget silently replaced; use??instead. - Chaining
?.too aggressively — it hides real bugs. Reserve it for properties that legitimately can be absent, not as a blanket crash guard. - Forgetting the method-call and bracket forms —
obj.fn?.()andarr?.[0]exist; plainobj?.fn()still throws iffnisundefined(it callsundefined()because the chain already resolved toobj).
Interview Questions
Q: What is the difference between ?? and ||?
||returns the right operand if the left is any falsy value (0, "", false, null, undefined, NaN).??returns the right operand ONLY if the left is null or undefined. Use??when values like 0, "", or false are valid.
Q: When would you use ?. instead of &&?
Use
?.when you want to safely access properties on values that might be null/undefined.&&short-circuits on any falsy value, which means it fails for valid values like 0, "", or false.?.only short-circuits on null/undefined.
Q: Can you combine ?. and ??? Give an example.
Yes:
const name = user?.profile?.displayName ?? "Anonymous". This safely navigates the chain and provides a default only when the result is null/undefined.
Q: What does ?. return when it hits null/undefined?
It short-circuits the rest of the chain and returns
undefined. Any remaining?.x,?.(), or?.[i]steps are skipped without throwing.
Q: What does 0 ?? 42 return? What about 0 || 42?
0 ?? 42returns0, because0is notnullorundefined.0 || 42returns42, because0is falsy and||falls back on any falsy value.
Quick Reference — Cheat Sheet
?. and ?? — QUICK MAP
Optional chaining ?. (safe access):
obj?.prop safe property access
obj?.fn?.() safe method call
arr?.[i] safe computed/index access
short-circuits to undefined on null/undefined
Nullish coalescing ?? (safe default):
value ?? fallback only fires for null / undefined
KEEPS 0, "", false, NaN as the actual value
Compare fallback triggers:
|| triggers on: 0, "", false, null, undefined, NaN
?? triggers on: null, undefined (nothing else)
Combined pattern:
const name = user?.profile?.name ?? "Anonymous"
Rule of thumb:
Access a maybe-missing chain -> ?.
Default when value is missing -> ??
Default when value is falsy -> || (rare, be intentional)
Previous: for...in vs for...of -> Keys vs Values Next: Array Methods -> find, some, every, flat, flatMap
This is Lesson 7.7 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.