# `??` vs `||`: Why Your Zero Keeps Disappearing

> The decisive difference between the nullish coalescing operator (??) and || — it's why 0 and empty strings get swallowed by defaults. Falsy traps, ??= assignment, optional chaining combos, and why mixing the two throws a SyntaxError.

**Published:** 2026-07-14 | **Updated:** 2026-07-14

---


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.

{{< img src="images/contents/zero-score.jpg" alt="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" caption="Photo: <a href='https://unsplash.com/photos/man-and-woman-standing-near-arcade-machine-during-nighttime-gbfqWoaTmdg' target='_blank' title='Opens in new window'>Unsplash</a> / <a href='https://unsplash.com/@introspectivedsgn' target='_blank' title='Opens in new window'>Erik Mclean</a>" >}}

## 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:

| Value | Type |
|----|------|
| `false` | Boolean |
| `0`, `-0`, `0n` | Number, BigInt |
| `""`, `''`, ` `` ` | String (empty) |
| `null` | Null |
| `undefined` | Undefined |
| `NaN` | Number |

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.

{{< img src="images/contents/filter-concept-en.png" alt="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" → `??`

{{< img src="images/contents/when-or-nullish-en.png" alt="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.

---

{{< faq >}}

## More from this series

- [Function Hoisting: Why You Can Call It Before You Declare It]({{< relref "javascript-function-hoisting" >}})
- [`Promise.all` vs `Promise.allSettled`: The Difference That Matters]({{< relref "javascript-promise-methods" >}})

