Use ?? For Defaults, Not ||
This JavaScript default-value bug is probably in your codebase right now. config.port || 3000 looks perfectly reasonable — and it silently rewrites a port of 0 to 3000, an empty prefix to your fallback string, and an explicit false to true. The fix is two characters: ??.
The bug: || tests truthiness, not presence
x || y returns y whenever x is falsy. JavaScript has six falsy values: false, 0, "" (empty string), null, undefined, and NaN. Four of those are perfectly valid configuration values:
const port = config.port || 3000; // 0 -> 3000 (bug)
const prefix = config.prefix || "app"; // "" -> "app" (bug)
const retries = config.retries || 5; // 0 -> 5 (bug)
const debug = config.debug || true; // false -> true (bug!)
The user set those values. || throws them away because it can't tell "absent" from "falsy". port: 0 is a real request — "give me a random free port" in Node's server.listen(0) — and debug: false is the single most common way to turn something off.
The fix: ?? tests nullish, not falsy
The nullish coalescing operator (??, ES2020) falls back only when the left side is null or undefined:
const port = config.port ?? 3000; // 0 stays 0
const prefix = config.prefix ?? "app"; // "" stays ""
const retries = config.retries ?? 5; // 0 stays 0
const debug = config.debug ?? true; // false stays false
Absent key? config.port is undefined, you get 3000. Explicit null? You get 3000. Anything else — including every falsy value the user meant — survives.
Rules of thumb
| Input value | x || 3000 | x ?? 3000 |
|---|---|---|
undefined | 3000 | 3000 |
null | 3000 | 3000 |
0 | 3000 | 0 |
"" | fallback | "" |
false | fallback | false |
NaN | fallback | NaN |
8080 | 8080 | 8080 |
Keep || only when you genuinely want to treat all falsy values as missing — e.g. normalizing user text where "" should become a placeholder. For config, options objects, and API responses, default with ??.
Power-user corner
??=for options objects (ES2021).options.timeout ??= 100assigns only when the property isnull/undefined— a one-liner replacement for theif (options.timeout == null)dance, and it short-circuits (the right side isn't even evaluated otherwise).- Pair with optional chaining.
customer?.address?.city ?? "Unknown"never throws on a missing branch and only defaults on true absence — the idiomatic deep-read-with-default. - Both short-circuit.
a ?? expensiveDefault()never calls the function whenais present — safe for lazy/costly fallbacks. - Parenthesize when mixing.
a || b ?? cis aSyntaxErrorby spec design; write(a || b) ?? c. The ambiguity was considered dangerous enough to ban outright. - Audit the ones you already shipped. Grep for
|| 0,|| '',|| false, and|| <number>on config/option reads — each is a candidate for the exact bug above.
Resources
- Nullish coalescing operator (
??) — MDN - Logical OR (
||) — MDN - Nullish coalescing assignment (
??=) — MDN - Optional chaining (
?.) — MDN - TC39 proposal: nullish coalescing
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.