JavaScript Interview Prep
Objects & Arrays

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 is null or undefined, the whole expression short-circuits to undefined instead of throwing Cannot 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 for null and undefined — so 0, '', and false keep flowing through as the real values they are. A port of 0 means "auto-assign", not "missing".

Mix them up and you get the classic production bug: port || 3000 silently overrides a valid 0. Use port ?? 3000 and 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 pattern user?.profile?.name ?? "Anonymous".

Read the full lesson -> [link]

#JavaScript #OptionalChaining #NullishCoalescing #ES2020 #InterviewPrep #Frontend #CodingInterview


Optional Chaining (?.) & Nullish Coalescing (??) thumbnail


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)

Optional Chaining (?.) & Nullish Coalescing (??) visual 1


Common Mistakes

  • Using || to supply defaults for configuration values — falsy but valid values like 0, "", or false get 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?.() and arr?.[0] exist; plain obj?.fn() still throws if fn is undefined (it calls undefined() because the chain already resolved to obj).

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 ?? 42 returns 0, because 0 is not null or undefined. 0 || 42 returns 42, because 0 is 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.

On this page