Stop Writing Nested If-Chains: Optional Chaining and Nullish Coalescing in Practice
Optional chaining (?.) and nullish coalescing (??) replace verbose nested null checks with one readable expression — here's how to use them well, and where they bite.
23 Aug 2026, 02:40 UTC

Every JavaScript developer who consumes API responses has written this pattern: if (user && user.address && user.address.street). It works, but it buries the actual intent — "get the street if it exists" — under three layers of defensive bookkeeping. Since ES2020, two operators let you say that intent directly: optional chaining (?.) and nullish coalescing (??). Used together, they replace most manual null-checking chains without changing your program's behavior.
The two operators, briefly
Optional chaining (?.) short-circuits property access. If the value to its left is null or undefined, the whole expression evaluates to undefined instead of throwing a TypeError. It also works for method calls (user.getName?.()) and computed access (user?.['address']).
Nullish coalescing (??) returns the right-hand operand only when the left-hand side is null or undefined. This is the key difference from ||: logical OR also falls back on falsy values like 0, '', and false, which are often legitimate data. ?? only treats "missing" as missing.
A worked example: rendering a user profile
Say an API returns a user object where address and its fields may be absent. The old-school version:
function getStreet(user) {
if (user && user.address && user.address.street) {
return user.address.street;
}
return '(no street)';
}With the modern operators, the same logic is one line:
const street = user?.address?.street ?? '(no street)';You can verify this in any modern browser console or Node.js 14+:
const user = { name: 'Ada', address: null };
console.log(user?.address?.street ?? '(no street)');
// Expected: "(no street)"And here's where ?? beats ||. If the API sends a numeric field where 0 is a real value:
const retries = config?.retries ?? 3;
// config.retries === 0 → retries is 0 (correct)
// config?.retries || 3 → retries is 3 (silently wrong)That distinction — falsy versus nullish — is the single most common bug these operators prevent.
Where this can go wrong
These operators are not free safety. Three limitations matter:
- Silence can hide bugs.
user?.address?.streetreturnsundefinedwhether the address is legitimately absent or your code passed the wrong variable. If a missing value indicates a real error, check explicitly and throw rather than chaining blindly. - Over-chaining hurts readability.
a?.b?.c?.d?.e?.fis technically valid but signals that the data shape is unpredictable — better to validate the payload once at the boundary (with a schema or a mapping function) than to sprinkle?.through every consumer. - Browser support. Both operators require ES2020. Every current browser and Node.js ≥ 14 supports them natively, but if you must support legacy environments like Internet Explorer, you need a transpiler such as Babel with
preset-env. Check the compatibility tables on MDN against your actual browser matrix before shipping untranspiled code.
Adopting them without regret
A practical rollout looks like this:
- Use
?.and??in all new code where you're reading potentially absent data. - Prefer
??over||for defaults, unless you genuinely want to fall back on any falsy value. - Add a lint rule — for example, a custom ESLint rule or code-review convention — flagging chains longer than two or three
?.segments as a sign the data should be normalized first. - If you target older browsers, confirm your build pipeline transpiles these operators, and spot-check the output bundle.
The takeaway: ?. and ?? don't just save keystrokes — they make "this value might be missing, and here's the fallback" an explicit, readable part of the expression. Reach for them at data boundaries, keep the chains short, and let ?? protect your legitimate zeros and empty strings.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.