Running a quiz service, we got a complaint one day: “I just took the test — why does it say ‘No score’?” We checked. Their score was zero. The database clearly stored 0, the API returned 0 faithfully. The culprit was a single line in the UI code.

javascript
const score = userScore || "No score";

|| returns the right side when the left is falsy. And in JavaScript, 0 is falsy — so are false, "", null, undefined, and NaN. An intentional value getting swapped for a default: this bug shows up far more often than you’d think.

An arcade scoreboard showing YOUR SCORE 0 - zero is very much a real score, but JavaScript's || operator treats 0 as falsy and swaps it for a default
An arcade scoreboard showing YOUR SCORE 0 - zero is very much a real score, but JavaScript's || operator treats 0 as falsy and swaps it for a default
Photo: Unsplash / Erik Mclean

How || works: falsy means the right side

|| returns the right-hand value when the left is falsy.

javascript
console.log(null || "default");      // "default"
console.log(undefined || "default"); // "default"
console.log(0 || "default");         // "default" ← not what you meant
console.log("" || "default");        // "default" ← not what you meant
console.log(false || "default");     // "default" ← not what you meant

JavaScript’s falsy values:

데이터 표
ValueType
falseBoolean
0, -0, 0nNumber, BigInt
"", '', ``String (empty)
nullNull
undefinedUndefined
NaNNumber

Of these, 0, "", and false can be meaningful values. That’s the || trap.

null and undefined, fine — but the number 0 meaning “nothing”? One wonders how the person who made that call in JavaScript’s earliest days feels about it now.

How ?? works: only null and undefined

?? (nullish coalescing) returns the right side only when the left is null or undefined.

javascript
console.log(null ?? "default");      // "default" ✅
console.log(undefined ?? "default"); // "default" ✅
console.log(0 ?? "default");         // 0        ✅ passes through
console.log("" ?? "default");        // ""       ✅ passes through
console.log(false ?? "default");     // false    ✅ passes through

Only null and undefined count as “no value”; everything else is treated as valid. Those two together are called nullish — which is where the operator’s name comes from.

A comparison of the two filters - the || filter blocks 0, empty string and false along with everything else falsy and replaces them with defaults, while the ?? filter blocks only null and undefined, letting 0, empty string and false pass through as valid values
A comparison of the two filters - the || filter blocks 0, empty string and false along with everything else falsy and replaces them with defaults, while the ?? filter blocks only null and undefined, letting 0, empty string and false pass through as valid values

By the way, mixing them is a syntax error

What happens if you put || and ?? in one expression without parentheses? It doesn’t even run.

javascript
const value = a || b ?? c;   // SyntaxError!
const value = (a || b) ?? c; // fine with parentheses

That’s not a bug — it was designed this way. People were bound to misremember the precedence between the two, so the spec stepped in as referee: “stop arguing and write your intent in parentheses.” A code review enforced by the language itself.

Patterns you’ll meet at work

Numeric settings

javascript
// users may legitimately set the timeout to 0
function createTimer(timeout) {
  // ❌ setting 0 gets replaced by the 3000 default
  const delay = timeout || 3000;

  // ✅ 0 is honored as a valid value
  const delay = timeout ?? 3000;
}

Empty strings

javascript
// users may intentionally leave the description blank
const description = userInput || "No description"; // ❌ blanks get replaced too
const description = userInput ?? "No description"; // ✅ only null/undefined do

API response defaults

javascript
const response = await fetchUserSettings();

// ❌ fontSize of 0 gets overwritten with 14
const fontSize = response.fontSize || 14;

// ✅ default applies only to explicit null/undefined
const fontSize = response.fontSize ?? 14;

Together with optional chaining

?? shines alongside ?. (optional chaining).

javascript
const user = {
  profile: {
    age: 0, // a 0-year-old user (rare, but valid)
  },
};

// ❌ age 0 displays as "No age on file"
const age = user?.profile?.age || "No age on file";

// ✅ default only when null/undefined
const age = user?.profile?.age ?? "No age on file";

Only when user?.profile?.age is undefined (property missing) does “No age on file” appear; a 0 shows as 0.

The ??= assignment operator

??= assigns only when the value is null or undefined. Handy for initializing settings.

javascript
const config = {
  theme: "dark",
  language: null,
};

// assign defaults only where null/undefined
config.language ??= "ko";    // was null → gets "ko"
config.theme ??= "light";    // has "dark" → untouched

console.log(config);
// { theme: "dark", language: "ko" }

We used to write this:

javascript
config.language = config.language ?? "ko";

??= cleans it right up.

When should you use ||?

?? isn’t always the answer. Sometimes 0, "", and false genuinely should fall back to a default.

javascript
// empty or missing input falls back to the default query
const query = searchInput || "All"; // "" → "All" is intended

// users without a role get the default one
const role = userRole || "guest"; // false/undefined/null all become "guest"

The rule of thumb:

  • If 0, "", false should also count as “no value” → ||
  • If only null/undefined count as “no value” → ??
A decision guide - before adding a default, ask whether 0, empty string and false are valid values in this spot. If valid, choose ??; if they should be treated as absent, choose ||. A note adds that when unsure, ?? is the safer baseline that loses less data
A decision guide - before adding a default, ask whether 0, empty string and false are valid values in this spot. If valid, choose ??; if they should be treated as absent, choose ||. A note adds that when unsure, ?? is the safer baseline that loses less data

When in doubt, ?? is the safer baseline — a mistake there means a default fails to appear, not a user’s zero evaporating.

Wrapping up

  • ||: returns the default for any falsy value (0, "", false, null, undefined, …)
  • ??: returns the default only for null or undefined
  • ??=: assigns only when the variable is null/undefined

Before you write a default, ask: “is 0 or an empty string a valid value here?” The answer tells you which operator to reach for.

The reply to that complaint was a one-character-class fix — || to ??. Apologies to the person who scored zero, but hey, we got this article out of it.


질문으로 다시 보기

What is the difference between ?? and ||?
|| returns the right-hand default whenever the left side is falsy (0, empty string, false, null, undefined, NaN), while ?? (nullish coalescing) returns the default only when the left side is null or undefined. If 0 or an empty string should survive as a valid value, use ??.
Why does mixing ?? and || throw a SyntaxError?
An expression like a || b ?? c without parentheses is defined as a syntax error by the JavaScript specification. Developers too easily misremember the precedence between the two operators, so the language forces you to state your intent explicitly: (a || b) ?? c.

More from this series