Nullish Coalescing: Fallbacks the Right Way

Posted by

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

Stop breaking on 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 or undefined.
  • 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";
  • ?. avoids Cannot 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 replaces null/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

Your email address will not be published. Required fields are marked *