Jan 28, 2023

What is a Nullish coalescing operator '??'

Tags

Have you ever logged a value, expected 0, and instead seen your “default” kick in and silently change the result? That was the exact moment the nullish coalescing operator ?? clicked for many JavaScript developers: it gives you defaults only when a value is truly missing, not just falsy like 0 or " ".

Why ?? solved a real bug for me


On one project, there was a pagination component where currentPage could legitimately be 0 for the first page. The code looked innocent:


const page = currentPage || 1;


Most of the time it worked—but whenever currentPage was 0, the expression treated it as “no value” and silently switched to 1. Users kept getting redirected to page 1 even though the API clearly returned 0. That bug cost a surprisingly long debugging session.

The fix was one tiny change:


const page = currentPage ?? 1;


Suddenly, 0 stayed 0. Only null or undefined triggered the default. That was the day ?? went from “new operator” to “must‑have tool” in the mental toolbox for many developers.


nullish-coalescing-operator-js


What the nullish coalescing operator actually does


The nullish coalescing operator ?? looks just like || at a glance, but the rule it follows is different:


  • value ?? fallback
- returns value if value is not null or undefined
- otherwise returns fallback

In other words:
  • Treat only null and undefined as “missing”
  • Keep 0false" ", and NaN as valid values

This makes it ideal when you want to distinguish between “not provided at all” and “provided, but falsy on purpose”.


Quick comparison: || vs ??


Consider this table of behaviors for the same value and different operators:


// Logical OR
console.log(0 || 10);          // 10
console.log("" || "default");  // "default"
console.log(false || true);    // true
console.log(null || "fallback"); // "fallback"
console.log(undefined || "fallback"); // "fallback"

// Nullish coalescing
console.log(0 ?? 10);          // 0
console.log("" ?? "default");  // ""
console.log(false ?? true);    // false
console.log(null ?? "fallback"); // "fallback"
console.log(undefined ?? "fallback"); // "fallback"


So the mental shortcut is:
  • Use || when you consider all falsy values as “empty” and want a default for them.
  • Use ?? when 0" ", and false are legitimate values you want to preserve.​

Everyday use cases where ?? shines


1. Setting default values without breaking 0 or " "


Imagine a user preferences object:


const settings = {
  theme: "dark",
  itemsPerPage: 0,        // 0 means "show all"
  nickname: "",           // empty string is allowed
};


If you write:


const itemsPerPage = settings.itemsPerPage || 10;
const nickname = settings.nickname || "Guest";


you accidentally overwrite legitimate values:
  • itemsPerPage becomes 10 instead of 0
  • nickname becomes "Guest" instead of ""

With ??:


const itemsPerPage = settings.itemsPerPage ?? 10; // stays 0
const nickname = settings.nickname ?? "Guest";    // stays ""


The defaults now only apply if the property is missing or explicitly null.

2. Optional function arguments


Sometimes you want to allow callers to deliberately pass 0 or false as values while still having a default when they omit the argument:


function createTimer(delayMs) {
  const delay = delayMs ?? 1000; // default only if undefined/null
  console.log(`Timer delay: ${delay}ms`);
}

createTimer();      // 1000
createTimer(0);     // 0 (allowed)
createTimer(null);  // 1000


This avoids the common trap of using delayMs || 1000, which would treat 0 as “no delay”.

3. Configuration and environment variables


In configuration files or environment variables, it is common to have explicit false or 0 values. Using ?? lets configuration stay in control:


const config = {
  logLevel: 0,          // 0 = only errors
  enableCache: false,   // explicitly disabled
};

const logLevel = config.logLevel ?? 2;      // 0 is respected
const enableCache = config.enableCache ?? true; // false is respected


Only if logLevel or enableCache are missing or null will the defaults kick in.

4. Dealing with messy API data


APIs sometimes return null when data is not present. ?? helps you convert those to sane defaults while still honoring valid zero‑like values:


const user = {
  name: "Jay",
  age: null,    // unknown age
  score: 0,     // score 0 is valid
};

const age = user.age ?? "N/A";      // "N/A"
const score = user.score ?? 100;    // 0 (kept)


If the backend later starts sending undefined instead of null, the same logic still works because ?? treats both as “missing”.

5. Combining with optional chaining


The combination of optional chaining ?. and ?? is extremely useful for deep data structures:


const city = user.profile?.address?.city ?? "Unknown";


This single line says:
  • If user.profile or address is missing, do not crash—just yield undefined.
  • If the final city is null or undefined, default to "Unknown".
  • But if city is an empty string, keep it as is.

This pattern is perfect when dealing with deeply nested API responses or form data where some branches might not exist.

When should you actually reach for '??' ?


A simple rule of thumb:
  • If your domain treats 0false, or "" as valid user choices → prefer ??.
  • If you really mean “anything falsy should fall back” → || is fine.

After using ?? for a while, it becomes the default choice for configuration, user input, component props, and any place where “missing” is different from “intentionally empty”.


EmoticonEmoticon