Stop breaking on 0, '', or false—why ?? is better than || for safe, predictable defaults in JavaScript and TypeScript.

0, '', or false—why ?? is better than || for safe, predictable defaults in JavaScript and TypeScript.Introduction
You’ve probably written this:
const port = config.port || 3000;
It looks fine until config.port = 0—a perfectly valid value—and your code “helpfully” replaces it with 3000. Oops.
This is the classic falsy trap of || (logical OR).
Enter nullish coalescing (??)—a small but powerful operator that fixes this. It only falls back when the value is null or undefined, not on falsy values like 0, false, or ''.
In this article, we’ll unpack **why ?? matters, when to use it, how it pairs with optional chaining (?.), TypeScript patterns, real-world use cases, and the common gotchas.
1) The Problem with ||
const count = 0;
console.log(count || 10); // 10 ❌ (lost a valid 0)
|| treats any falsy value (0, '', false, NaN) as a trigger for fallback.
That’s fine in boolean logic, but not in default assignment.
2) The Fix: Nullish Coalescing ??
const count = 0;
console.log(count ?? 10); // 0 ✅
- Only falls back when the left side is
nullorundefined. - Preserves all other falsy values.
Definition:
x ?? y // means (x !== null && x !== undefined) ? x : y
3) Common Patterns
A) Safe defaults
const retries = config.retries ?? 3;
const timeout = config.timeout ?? 5000;
B) Preserve empty strings
const name = user.name ?? "(anonymous)";
console.log(name); // '' stays '' if set
C) Preserve boolean flags
const darkMode = settings.darkMode ?? true;
// false stays false, only null/undefined replaced
D) Nested optionals with ?.
const host = config?.server?.host ?? "localhost";
E) Function params
function connect(url, port = 80) {
const effectivePort = port ?? 80; // ensures null/undefined replaced
}
4) Real-World Use Cases
Config merging
const cfg = {
port: env.PORT ?? 3000,
debug: env.DEBUG ?? false,
};
API parsing
const age = apiResponse.age ?? null; // ensures explicit null if missing
React props
function Button({ label, disabled }) {
return <button disabled={disabled ?? false}>{label ?? "Click"}</button>;
}
Form handling
const value = formState.values[field] ?? "";
Redux state
const theme = state.settings?.theme ?? "light";
5) Pairing ?? with Optional Chaining
Optional chaining and nullish coalescing are a dream team:
const email = user?.profile?.contact?.email ?? "N/A";
?.avoidsCannot read property of undefined.??ensures fallback only when the final value is nullish.
6) TypeScript Patterns
A) Safe config defaults
type Config = { retries?: number; verbose?: boolean };
function run(cfg: Config) {
const retries = cfg.retries ?? 3; // keeps 0 if explicitly set
const verbose = cfg.verbose ?? false;
}
B) Optional fields with strict null checks
interface User { name?: string | null }
const username = user.name ?? "Guest";
C) Partial overrides
type Options = { port?: number; debug?: boolean };
const DEFAULTS: Options = { port: 3000, debug: false };
function merge(overrides: Options): Options {
return {
port: overrides.port ?? DEFAULTS.port,
debug: overrides.debug ?? DEFAULTS.debug,
};
}
7) Edge Cases & Gotchas
Only nullish triggers fallback
console.log(false ?? true); // false
console.log(0 ?? 42); // 0
console.log('' ?? 'hi'); // ''
Short-circuiting
let log = () => console.log("ran");
undefined ?? log(); // log() runs (fallback evaluated)
Fallback expression is always evaluated if left is nullish.
Mixing with ||
const value = input ?? default || backup;
Use parentheses to make intent clear.
Mixing with ternary
Prefer ?? over verbose ternaries:
const port = config.port != null ? config.port : 3000;
8) When to Use ?? vs ||

Example:
isLoggedIn = user || fallbackUser;→ want truthy fallback.page = query.page ?? 1;→ want 0 preserved.
9) Performance & Compatibility
- Native in modern browsers and Node 14+.
- For older environments, Babel/TypeScript transpiles
??to equivalent checks. - Performance impact negligible; readability gain huge.
10) Handy Recipes
Coalesce chain
const theme = user?.settings?.theme ?? env.THEME ?? "light";
Avoid undefined in JSON
const payload = {
id: user.id,
email: user.email ?? null,
};
With maps/dicts
const score = scores[playerId] ?? 0;
With optional functions
const result = maybeFn?.() ?? defaultValue;
Conclusion
?? is one of those “small but mighty” operators that saves you from subtle bugs:
- Unlike
||, it only replacesnull/undefined. - Keeps valid falsy values (
0,'',false). - Plays perfectly with optional chaining.
- Ideal for configs, API responses, props, and defaults.
Pro Tip: Default to ?? for fallbacks. Reach for || only when you explicitly want truthy/falsy logic.
Call to Action (CTA)
What’s the nastiest bug you’ve seen from using || instead of ???
Share your story in the comments—and forward this to a teammate who still defaults ports with ||.


Leave a Reply