Optional Chaining, Nullish Coalescing, Logical Assignment
TL;DR
Three small operators that change how you write defensive code: ?. (optional chaining — short-circuits on null/undefined), ?? (nullish coalescing — falls back on null/undefined only, not on falsy ""/0), and logical assignment (??=, ||=, &&=) for “assign if condition.” Together they replace huge swaths of if (x && x.y && x.y.z) and x.y || default boilerplate — and the ?? vs || distinction is the most common gotcha because they look interchangeable until they aren’t.
Interview Q&A
Q: Optional chaining — what does it do?
A: Short-circuits at the first null or undefined in a chain, returning undefined instead of throwing.
const user = { profile: { name: "Ada" } };
user.profile?.name; // "Ada"
user.profile?.address?.city; // undefined (doesn't throw)
user.profile?.address.city; // throws — `?.` only protects the link it's on
Works for:
- Property access:
obj?.prop - Index access:
arr?.[0] - Function/method calls:
fn?.(),obj.method?.()
Stops the chain immediately on a nullish intermediate — no exception, no further evaluation.
Q: Common mistakes with ?..
A:
// Wrong — protects only `users`, not `users[0]`
users?.[0].name; // throws if users[0] is undefined
// Right
users?.[0]?.name;
// Wrong — `?.` doesn't prevent the assignment crashing
user?.profile.name = "Bob"; // SyntaxError — `?.` not allowed on assignment LHS
// Right — guard separately
if (user?.profile) user.profile.name = "Bob";
Also: ?. can’t be the target of delete, ++/--, or assignment. It’s read-only.
Q: ?? vs || — when each?
A: The most asked question in this group:
| | || | ?? |
|—|—|—|
| Falls back on | any falsy: null, undefined, 0, "", false, NaN | only null or undefined |
| When use | “non-empty default” | “default for missing value” |
const port = config.port ?? 3000; // if not set, use 3000 — explicit port=0 is preserved
const port = config.port || 3000; // port=0 would fall back — BUG if 0 was intentional
const name = data.name ?? "anon"; // empty string "" is preserved
const name = data.name || "anon"; // empty string falls back to "anon"
?? is safer for numeric or empty-string config. Use || only when you actually want all falsy values to fall back (rare in modern code).
Q: Logical assignment — when use?
A: Conditional assignment in one expression:
x ??= 5; // x = x ?? 5 — assign only if x is null/undefined
x ||= 5; // x = x || 5 — assign only if x is falsy
x &&= 5; // x = x && 5 — assign only if x is truthy
// Common uses:
config.timeout ??= 30_000; // set default if not provided
cache[key] ??= computeExpensive(); // memoize lazily
loaded[id] = (loaded[id] ?? 0) + 1;
Note: the RHS is only evaluated if the assignment fires. cache[key] ??= computeExpensive() doesn’t call computeExpensive() when cache[key] is already set.
Q: Combining ?. with default — what about ?.x ?? default?
A: Natural composition:
const cityName = user?.profile?.address?.city ?? "Unknown";
If any link is nullish, the chain produces undefined; ?? falls back to "Unknown". This pattern replaces:
let cityName = "Unknown";
if (user && user.profile && user.profile.address) {
cityName = user.profile.address.city || "Unknown";
}
Five lines of defensive code become one.
Q: Type narrowing — does TS understand ?.?
A: Yes. After a ?. chain, the result type is T | undefined. Combined with ?? it narrows to T:
const cityName: string | undefined = user?.profile?.city;
const cityName: string = user?.profile?.city ?? "Unknown";
For control flow:
if (user?.profile?.city) {
user.profile.city.toUpperCase(); // narrowed — TS knows everything is non-nullish here
}
Q: Performance — does ?. slow things down?
A: Negligible. Optional chaining compiles to runtime null checks; modern engines optimize these away in hot paths. Don’t avoid ?. for “perf.” Avoid it for “wrong semantics” — e.g., when you actually want to throw on nullish (use an assertion or explicit check).
Q: When should you not use ?.?
A:
- When
nullshould be an error. A?.swallows the bug silently —user?.idreturnsundefinedfor an unauthenticated user instead of revealing the auth bug. Use explicit checks + throws/asserts at boundaries. - When a non-null parent invariant exists — over-using
?.reads as “I don’t trust my own types.” Trust your types; reach for?.only when nullishness is genuinely possible.
Q: Show me the null/undefined distinction.
A: Both are nullish. ?? and ?. treat them identically. JS still has both:
undefined— implicit absence (declared but unassigned, missing property, missing argument).null— explicit absence (deliberately “no value”).
TS lets you distinguish (x: null vs x: undefined). At runtime, the two only differ in:
typeof nullis"object"(legacy bug);typeof undefinedis"undefined".- JSON.stringify drops
undefinedproperties; keepsnull.
For most code, treat them as the same. The ?? operator’s whole point is unifying them.
Gotchas / edge cases
?.()on a non-function throwsTypeError: obj.method is not a functionifobj.methodexists but isn’t callable.?.only checks for nullish.- Short-circuit evaluation stops the whole chain —
a?.b.c()doesn’t callb.c()ifais nullish; but ifais set andbis nullish, throws onb.c. Usea?.b?.c(). ??precedence —a ?? b || cis a syntax error. Parens required:(a ?? b) || c. Same with&&.- JSON output of
??=with arrays/objects — assigns the reference, not a clone. Mutations to the assigned object reflect back. - Optional chaining with
delete—delete a?.bdoesn’t throw ifais nullish; otherwise deletes. Niche but legal. - TypeScript
strictNullChecksinteraction —?.is necessary; without strict, you might write code that crashes at runtime that TS didn’t flag.
What a senior is expected to say
- “
?.short-circuits on nullish, returningundefinedinstead of throwing. Each link needs its own?.—a?.b.conly protectsa.” - “
??vs||:??only falls back on nullish;||on any falsy. Use??for numeric/empty-string defaults so0and''aren’t accidentally replaced.” - “Logical assignment (
??=etc.) for one-line conditional assigns — common for default config and lazy memoization.” - “Don’t
?.to hide bugs — ifnullshould be an error, throw. Use?.when nullishness is genuinely expected.” - “
?.doesn’t compose with assignment (x?.y = 1is invalid). Guard separately.”
Cross-references
- TypeScript narrowing: ../04_typescript/05_narrowing_and_guards.md
- Modern array/object methods (often combine with
?.): 05_modern_collection_methods.md
Further reading
- MDN — Optional chaining: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
- MDN — Nullish coalescing: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing
- TC39 — Logical Assignment Operators: https://github.com/tc39/proposal-logical-assignment