Nullish coalescing & optional chaining
Handle missing values with ?? and ?. instead of long checks, while preserving useful values like 0, false, and the empty string.
- 01Choose the right backupUse ?? when only null and undefined should fall back, and know when || is still right.
- 02Read safely through missing dataUse ?. on properties, brackets, and calls without hiding real TypeErrors.
- 03Fill or update values in placeUse ??=, ||=, and &&= with a clear mental model of what each one tests.
Missing, not merely empty
The previous Logical operators lesson showed why count || 10 is dangerous when count can be 0. JavaScript now has a sharper tool for that exact problem: nullish coalescing, written ??.
Nullish means exactly null or undefined. It does not mean every falsy value. A muted volume of 0, an answered checkbox of false, and an intentionally blank string can all be real data.
Use ?? when only null and undefined should fall back, and use ?. when a property or call is genuinely optional and should become undefined instead of throwing.
?? is a backup planImagine arranging a trip. If the first plan is completely missing, you use the backup. But if the plan says “walk zero blocks” or “do not send a reminder,” that is still a plan. ?? is that careful backup rule.
- In real life: No plan at all
- In JavaScript:
nullorundefined: use the backup - In real life: A plan that costs ₹0
- In JavaScript:
0: keep it - In real life: A quiet no
- In JavaScript:
false: keep it - In real life: A blank label chosen on purpose
- In JavaScript:
"": keep it
Where the analogy stops: A human might ask follow-up questions about whether a blank plan is useful. JavaScript follows the exact rule: only null and undefined trigger ??.
Optional chaining, written ?., solves a related problem. It lets you read through data that may be incomplete without writing the same long guard again and again.
The ?? backup operator
INTERACTIVEleft ?? right asks one narrow question: “is left null or undefined?” If yes, it returns right. Otherwise it returns left unchanged.
const displayName = user.name ?? "Anonymous";
const volume = savedVolume ?? 50;
const city = userCity ?? "Unknown";This is not type conversion. JavaScript is not turning 0 into true or false before deciding. It is checking for the two missing-value markers from the Booleans, null & undefined lesson.
const value = 0;console.log(value || "default");console.log(value ?? "default");- value
- 0
value || "default"- default
value ?? "default"- 0
The two operators differ here. || replaces any falsy value; ?? replaces only null or undefined.
| Value | With || | With ?? | Lesson |
|---|---|---|---|
0 | "default" | 0 | Yes — 0 is falsy but not nullish. |
"" | "default" | "" | Yes — an empty string can be a deliberate value. |
false | "default" | false | Yes — false is a real boolean answer. |
NaN | "default" | NaN | Yes — NaN is falsy, but it is not null or undefined. |
null | "default" | "default" | No — both fall back. |
undefined | "default" | "default" | No — both fall back. |
So || is not wrong. It is just a different question: “is the left side falsy?” Use it when blanks, zero, false, and NaN should all be rejected. Use ?? when those values might be meaningful.
Optional chaining on properties, brackets, and calls
STEP THROUGHIn the Logical operators lesson you saw guard chains like user && user.address && user.address.city. Optional chaining is the direct version: user?.address?.city.
?. checks that the door existsWalking down a hallway, you check that the next door exists before trying to open it. If there is no door, you stop instead of crashing into a wall. That is ?.: stop at missing links and produce undefined.
- In real life: A hallway has no next door
- In JavaScript: The value before
?.isnullorundefined - In real life: Stop calmly
- In JavaScript: The chain returns
undefined - In real life: The door exists
- In JavaScript: JavaScript opens it and continues the read
- In real life: A sign says door, but it is not a real door
- In JavaScript: A property exists but is not a function:
?.()can still throw
Where the analogy stops: The analogy explains missing links. It does not mean optional chaining validates every value or fixes wrong data shapes.
Optional chaining has three common forms: property reads, obj?.name; bracket reads, arr?.[0] or obj?.[key]; and optional calls, fn?.() or obj.method?.().
Switch the starting user, then step through real optional chaining results. Predict whether each read returns a value, undefined, or a caught TypeError.
script
let checks = 0;function mark(label) { checks += 1; return label;}function riskyCity() { try { return user.address.city; } catch (error) { return error.name; }}const safeCity = user?.address?.[mark("city")];const firstTag = user?.tags?.[0];const greeting = user?.greet?.();const noFunction = undefined;const optionalCall = noFunction?.();const notCallable = (() => { try { return ({ run: 7 }).run?.(); } catch (error) { return error.name; }})();const missingUser = null;const skipped = missingUser?.[mark("skipped")];console.log(riskyCity(), safeCity, firstTag, greeting, optionalCall, notCallable, checks, skipped);Notice the precise limit: ?. checks only the value immediately before it. It short-circuits when that value is null or undefined. If the property exists but is the wrong kind of value, normal JavaScript rules still apply.
??=, ||=, and &&=
STEP THROUGHLogical assignment operators update a variable or property only when the matching logical operator says to. They are shortcuts, but they are also readable when the intent is simple.
x ??= y: fill this box only if it is empty, meaning nullish.x ||= y: replace it if it is falsy.x &&= y: update it only if something truthy is already there.
Step through logical assignment. Watch how ??= keeps 0, ||= replaces an empty string, and &&= updates only an existing truthy value.
script
const user = { nickname: " Ada " };settings.volume ??= 50;settings.theme ||= "light";settings.retries &&= settings.retries + 1;user.nickname &&= user.nickname.trim();console.log(settings.volume, settings.theme, settings.retries, user.nickname);The most important beginner mistake is using ||= for settings where 0 is meaningful. A muted volume should stay muted.
Rules and syntax traps
SYNTAXJavaScript intentionally rejects a few expressions that look tempting. The first rule: do not mix ?? with && or || unless you add parentheses.
const a = null;
const b = "backup";
const c = "last";
console.log(a || b ?? c);const a = null;
const b = "backup";
const c = "last";
console.log((a || b) ?? c);The second rule: optional chaining cannot be the target of assignment. This is invalid because JavaScript cannot store a property on “maybe nothing.”
const obj = { a: 1 };
obj?.a = 2;Do not sprinkle ?. everywhere just to avoid errors. If a value must exist, an early TypeError may be the clearest signal that your data or program state is wrong.
Where you’ll use this
You will see these operators around profiles, configuration, optional callbacks, and data from outside your program. Objects and arrays get their own full lessons in Stage 3, so read the examples as simple containers: { name: "Ada" } has named properties, and ["admin"] has items by index.
const user = { name: "Asha", address: { city: "Pune" }, nickname: "" };
const displayName = user.nickname ?? user.name ?? "Guest";
const city = user.address?.city ?? "Unknown";
const firstRole = user.roles?.[0] ?? "reader";
console.log(displayName, city, firstRole);Line 2 keeps an empty nickname because ?? treats it as present. If your product says blank nicknames should fall back, that specific line should use || instead. Operator choice follows the product rule.
- A saved volume can be
0, meaning muted. - A product price can legitimately be
0for a free item. - A username must not be empty, so
""should use Guest. - A menu label should fall back when it is
"". - A saved checkbox can be
false, meaning the person opted out. - A search box should show
All itemswhen the query is blank.
Sort each scenario by whether falsy values should be preserved or rejected.
Common misconceptions
- “Nullish means falsy.” No: nullish means only
nullorundefined. - “
??is a better||.” It is different, not universally better. - “
?.makes a whole expression safe.” It protects only the link where you put it. - “
method?.()checks whether it is a function.” It skips missing methods, but non-functions still throw. - “Logical assignment is just shorter.” It also communicates the exact condition for assigning.
- “Use
?.everywhere.” Required data should fail loudly so bugs are visible.
| Tool | Question it asks | Good for | Careful |
|---|---|---|---|
?? | Is the value null or undefined? | Preserving 0, false, and blank strings | Not a blank-string validator |
|| | Is the value falsy? | Rejecting any blank, zero, false, or NaN value | Can overwrite real choices |
?. | Is this link nullish? | Reading optional nested data | Can hide bugs if the data is required |
??= | Is this place nullish? | Filling missing settings | Keeps 0 by design |
||= | Is this place falsy? | Replacing empty UI labels | Overwrites 0 and false |
&&= | Is this place truthy? | Updating existing strings or flags | Skips empty strings and 0 |
Practice exercises
5 EXERCISESRun the code in your head. Type both output lines in order.
const saved = 0;
console.log(saved || 10);
console.log(saved ?? 10);const saved = 0;
console.log(saved || 10);
console.log(saved ?? 10);The first log uses ||, so 0 falls back to 10. The second log uses ??, so 0 is preserved.
The app should keep a saved volume of 0. Change the defaulting operator.
const savedVolume = 0;
const volume = savedVolume || 50;
console.log(volume);const savedVolume = 0;
const volume = savedVolume ?? 50;
console.log(volume);Use ?? so only null or undefined falls back. The fixed program prints 0.
?.Replace the repeated && checks with optional chaining.
const city = user && user.address && user.address.city;
console.log(city || "Unknown");const user = { address: { city: "Pune" } };
console.log(user?.address?.city ?? "Unknown");user?.address?.city ?? "Unknown" is the direct version. It avoids the long && chain and preserves non-nullish values.
??=Write two logical assignment lines that keep the existing volume but add a theme.
const settings = { volume: 0 };
// Fill volume with 50 only if missing.
// Fill theme with "light" only if missing.
console.log(settings.volume, settings.theme);const settings = { volume: 0 };
settings.volume ??= 50;
settings.theme ??= "light";
console.log(settings.volume, settings.theme);settings.volume ??= 50 keeps 0. settings.theme ??= "light" fills the missing theme.
?? and ||What must you add to make line 4 valid JavaScript?
const a = null;
const b = "backup";
const c = "last";
console.log(a || b ?? c);const a = null;
const b = "backup";
const c = "last";
console.log((a || b) ?? c);JavaScript requires parentheses when ?? is mixed with || or &&. (a || b) ?? c is valid and prints backup.
Check your understanding
7 QUESTIONSQuestion 1 of 7When does
left ?? rightuseright?Choose an answer to see the explanation.
Question 2 of 7What does the
count ?? 10snippet print?Read the code, then predictconst count = 0; console.log(count ?? 10);Choose an answer to see the explanation.
Question 3 of 7What does the complete user optional chain print?
Read the code, then predictconst user = { address: { city: "Pune" } }; console.log(user?.address?.city);Choose an answer to see the explanation.
Question 4 of 7What does the null user fallback print?
Read the code, then predictconst user = null; console.log(user?.name ?? "Guest");Choose an answer to see the explanation.
Question 5 of 7Which line can throw even though it uses
?.?Choose an answer to see the explanation.
Question 6 of 7What does the
??=volume snippet print?Read the code, then predictconst settings = { volume: 0 }; settings.volume ??= 50; console.log(settings.volume);Choose an answer to see the explanation.
Question 7 of 7Which expression is valid JavaScript?
Choose an answer to see the explanation.
Key takeaways
??falls back only fornullandundefined.||falls back for every falsy value, so it can erase 0, false, and blank strings.?.returnsundefinedwhen the link before it is nullish.?.()skips missing calls, but existing non-functions still throw TypeError.??=,||=, and&&=assign only when their matching logical test passes.- Parenthesize when mixing
??with&&or||.
Final definition: ?? supplies a backup for missing values, and ?. safely stops optional property or call chains at missing links.
Up next: The rest of the operators.