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
null
orundefined
. - 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