Master JavaScript optional chaining (?.) and nullish coalescing (??): the ?? vs || difference, method call pitfalls, ??= ||= &&= logical assignment operators, and short-circuit evaluation. 9 interview questions with answers.
Picture a package delivery system with multiple checkpoints. A package must pass through a warehouse, then a local depot, then a delivery van, and finally reach your door. If the warehouse burns down, the old system sends a truck to the ashes, then tries to load from a pile of rubble, then crashes the van into a closed building. The new system checks at each step: "Does this checkpoint exist?" If not, it stops immediately and hands you a note saying "not available" instead of crashing. Optional chaining is that intelligent delivery check. Instead of throwing an error when you try to access a property on something that does not exist, it short-circuits and returns undefined. You ask for the value if it exists; you get undefined if it does not. Nullish coalescing is the note. When you get undefined or null back, it gives you a fallback value you specify. And crucially, it only triggers on null and undefined — not on false, 0, or an empty string, which are legitimate values that should be kept. The old way of providing defaults used the || operator, which treats all falsy values as "missing" and incorrectly replaces zeros, empty strings, and false with the default. Nullish coalescing fixes that specific problem.
Accessing properties on nested objects requires checking every intermediate value. Before optional chaining, even one null or undefined in the chain would crash with a TypeError.
const user = {
profile: {
address: {
city: 'Mumbai'
}
}
}
// Safe access before optional chaining — verbose and error-prone
const city = user && user.profile && user.profile.address && user.profile.address.city
// If any API response is incomplete:
const incompleteUser = { profile: null }
const city2 = incompleteUser.profile.address.city
// TypeError: Cannot read properties of null (reading 'address')
The ?. operator evaluates the left side. If it is null or undefined, it short-circuits and returns undefined without evaluating the right side. If it has a value, evaluation continues normally.
const user = {
profile: {
address: { city: 'Mumbai' }
}
}
// Optional chaining — clean and crash-safe
const city = user?.profile?.address?.city
console.log(city) // 'Mumbai'
// When any part of the chain is null/undefined — returns undefined, no crash
const incompleteUser = { profile: null }
const city2 = incompleteUser?.profile?.address?.city
console.log(city2) // undefined — no TypeError ✓
Optional chaining works in four forms:
// 1. Property access
user?.profile?.address
// 2. Bracket notation — dynamic property names
user?.settings?.['theme-color']
const key = 'firstName'
user?.profile?.[key]
// 3. Method calls — only calls the method if it exists
user?.getAddress?.() // calls getAddress() if it exists
// returns undefined if user or getAddress is null/undefined
// 4. Optional function call — for when the value itself might not be a function
const callback = null
callback?.() // returns undefined — no TypeError
const valid = () => 42
valid?.() // returns 42 — called normally
The position of ?. matters. It checks whether the thing immediately to its left is null or undefined.
const user = {
getName: null,
getAddress: function() { return null },
}
// Correct: checks if getName exists before calling it
user?.getName?.() // undefined — getName is null, short-circuits before call
// Wrong: checks if user exists, then tries to call getName (which is null)
user?.getName() // TypeError: user.getName is not a function
// Because: user exists, so ?. passes — then getName() is called on null
// Chaining into a method's return value
user?.getAddress?.()?.city // undefined — getAddress() returns null, ?. handles it
The ?? operator returns its right-hand side only when its left-hand side is null or undefined. All other values, including false, 0, NaN, and "", pass through unchanged.
// ?? — only triggers on null/undefined
console.log(null ?? 'default') // 'default'
console.log(undefined ?? 'default') // 'default'
console.log(0 ?? 'default') // 0 — 0 is a valid value
console.log(false ?? 'default') // false — false is a valid value
console.log('') ?? 'default') // '' — empty string is valid
console.log(NaN ?? 'default') // NaN — passes through
The critical difference from ||:
// || triggers on any falsy value — WRONG for these cases
const port = userInput.port || 3000 // bug: port 0 becomes 3000
const isEnabled = settings.enabled || true // bug: false becomes true
const name = response.name || 'Guest' // bug: empty string "" becomes 'Guest'
const count = data.count || 0 // bug: redundant but can shadow 0
// ?? triggers only on null/undefined — CORRECT
const port2 = userInput.port ?? 3000 // 0 stays 0 ✓
const isEnabled2 = settings.enabled ?? true // false stays false ✓
const name2 = response.name ?? 'Guest' // '' stays '' ✓
const count2 = data.count ?? 0 // 0 stays 0 ✓
Optional chaining and nullish coalescing are designed to work together. ?. safely navigates, ?? provides the fallback for when navigation produces nothing.
const config = {
theme: {
dark: {
background: '#1a1a1a'
}
}
}
// Get value or fallback to default
const bg = config?.theme?.dark?.background ?? '#ffffff'
const fontSize = config?.theme?.dark?.fontSize ?? 16
// API response handling
const users = response?.data?.users ?? []
const total = response?.meta?.total ?? 0
const userName = response?.user?.name ?? 'Anonymous'
// Event handling
const value = event?.target?.value?.trim() ?? ''
ES2021 introduced three logical assignment operators that combine assignment with logical evaluation. They are shorthand for common conditional assignment patterns.
// ??= — nullish assignment
// Assigns right side only if left side is null or undefined
let a = null
a ??= 'default'
console.log(a) // 'default' — was null, so assigned
let b = 0
b ??= 'default'
console.log(b) // 0 — was not null/undefined, unchanged
// Equivalent to:
a = a ?? 'default'
// ||= — logical OR assignment
// Assigns right side if left side is falsy
let c = ''
c ||= 'fallback'
console.log(c) // 'fallback' — '' is falsy, so assigned
// &&= — logical AND assignment
// Assigns right side only if left side is truthy
let d = 'Alice'
d &&= d.toUpperCase()
console.log(d) // 'ALICE' — was truthy, so assigned
let e = null
e &&= e.toUpperCase()
console.log(e) // null — was falsy, so unchanged (no crash)
These operators are lazy — the right side is never evaluated if the assignment won't happen. This is important for performance and for avoiding side effects.
Optional chaining's short-circuit is complete — once it encounters null or undefined, the entire remaining expression to the right is not evaluated. This prevents side effects in the bypassed code.
let count = 0
const obj = null
obj?.method(count++) // count++ never runs — short-circuited
console.log(count) // 0 — side effect was prevented
// Delete with optional chaining
delete obj?.property // no-op if obj is null — does not throw
// With arrays
const arr = null
arr?.[0] // undefined — no TypeError
arr?.map(x => x * 2) // undefined — map is never called
When combining ?. with ||, the precedence and short-circuit interactions produce non-obvious output. This appears frequently as an output prediction question.
const obj = { value: 0 }
console.log(obj?.value || 'missing') // 'missing' — value is 0 (falsy), || kicks in
console.log(obj?.value ?? 'missing') // 0 — 0 is not null/undefined, ?? does not kick in
const obj2 = null
console.log(obj2?.value || 'missing') // 'missing' — undefined is falsy
console.log(obj2?.value ?? 'missing') // 'missing' — undefined triggers ??
Both produce 'missing' when the object is null — but they differ when the value is 0, false, or ''. Using || instead of ?? when accessing values that can legitimately be falsy is a real production bug.
Many devs think ?. checks if the property exists — but actually it checks if the value to its LEFT is null or undefined. If the left side has a value, evaluation continues regardless of whether the accessed property exists. obj?.foo returns undefined if obj is null, but also returns undefined if obj is {} and foo does not exist — but for different reasons: the first is optional chaining short-circuiting, the second is a normal property access returning undefined.
Many devs think ?? and || are interchangeable — but actually they treat the falsy value set differently. || triggers on any falsy value: false, 0, NaN, empty string, null, undefined. ?? triggers only on null and undefined. This difference matters critically for numeric inputs where 0 is valid, for boolean flags where false is valid, and for string fields where an empty string is a real value.
Many devs think user?.getName() checks if getName exists before calling it — but actually the ?. in user?.getName() only checks if user is null or undefined. It does not check if getName is a function. If user exists but getName is null, this still throws a TypeError. The correct form when getName itself might not be a function is user?.getName?.() — two optional chaining operators.
Many devs think ??= is the same as ||= — but actually ??= only assigns when the left side is null or undefined, while ||= assigns when the left side is any falsy value. a ??= 'default' leaves a = 0 unchanged because 0 is not null or undefined. a ||= 'default' replaces a = 0 with 'default' because 0 is falsy.
Many devs think optional chaining can replace null checks everywhere — but actually it silently swallows errors that should surface. If a property should always exist but does not due to a bug, optional chaining masks the problem by returning undefined instead of crashing. Use optional chaining deliberately for genuinely optional data, not as a way to suppress all errors.
Many devs think you cannot use delete with optional chaining — but actually delete obj?.property is valid and safe. If obj is null or undefined, the delete is a no-op instead of throwing. This is useful when cleaning up properties that may or may not exist.
API response handling is the most common real-world application of optional chaining — when a REST API returns a user object that may or may not have a nested profile, nested settings, or nested billing data depending on user type, optional chaining replaces defensive checks that were previously three to five lines per property access and makes the code significantly easier to read and audit.
React component props are frequently optional by design — a Card component might receive an optional author object with an optional avatar URL, and using author?.avatar?.url ?? '/default-avatar.png' in a single expression replaces the conditional rendering chain that was previously required to avoid crashing on undefined.
Form default values consistently use nullish coalescing rather than OR — a text input showing a user's saved name should show the empty string if the user deliberately saved an empty name, so name ?? '' is correct but name || '' incorrectly replaces a saved empty string with the default fallback, corrupting the user's intentional data.
TypeScript's strict null checks are designed to work alongside optional chaining — the TypeScript compiler understands ?. and narrows types correctly after its use, producing the same safety guarantees as explicit null checks but with significantly less code. This is why codebases that adopted strict mode simultaneously adopted optional chaining as a primary defensive pattern.
Node.js configuration loading uses ??= extensively — when loading environment variables and providing code defaults, process.env.PORT ??= '3000' sets the default only when the variable is not set, correctly leaving a PORT value of '0' (a valid port) unchanged, whereas ||= would incorrectly override it.
Vue 3's template expressions and React's JSX both support optional chaining natively since the language feature is part of ES2020 — framework documentation and code reviews across the industry now treat optional chaining as standard and its absence in nested property access as a code smell worth flagging.
What is optional chaining (?.) and nullish coalescing (??)?
Optional chaining prevents TypeError on missing properties
JavaScript Optional Chaining and Nullish Coalescing
Chaining Promises with Optional Chaining and Nullish Coalescing
Resolving Nested Promises with Optional Chaining and Nullish Coalescing
Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.