Both are backup plans. If your value isn't there, use the backup. The or sign is picky and throws out zero and empty strings too. The double question mark only uses the backup when the box is really empty, meaning null or undefined.
Two backup plans
a || b gives you a if it is truthy, and b otherwise. Falsy values are false, 0, "" (empty string), null, undefined and NaN. So || swaps out all of them.
a ?? b gives you a unless it is null or undefined. Only then does it use b. The fancy name for ?? is the nullish coalescing operator, and "nullish" just means null or undefined.
console.log(0 || 10); // prints: 10
console.log(null || 10); // prints: 10
console.log(0 ?? 10); // prints: 0
console.log(null ?? 10); // prints: 10
console.log(undefined ?? 10); // prints: 10
The same goes for an empty string. "" || "Guest" gives "Guest", while "" ?? "Guest" keeps the empty string.
There are two because || is older. People used it for backups for years, but it treats every falsy value as empty. ?? came later for the cases where zero or an empty string is a real answer.
Note that neither operator turns things into true or false. Both hand back one of the two actual values, either the one on the left or the backup on the right. Also, the right side only runs when it is needed, so a backup like loadSettings() is never called if the left side is kept.
The zero bug
Your volume is zero, on purpose. You want a backup of 10 only if no volume was set.
const volume = 0;
console.log(volume || 10); // prints: 10
console.log(volume ?? 10); // prints: 0
With || you get 10, which is wrong. Zero is falsy, so || throws it away. With ?? you get 0, which is what you meant. This is the same bug you see when people write defaults by hand with ||, and the fix is the same.
Which one to use
- Use
??when zero or an empty string is a real answer. Volume, scores, counts, prices and text fields are common examples. - Use
||when any falsy value should get the backup. For example, a display name where an empty string should become "Guest".
const nickname = "";
console.log(nickname || "Guest"); // prints: Guest
If you're not sure, ?? is usually the safer choice, because it only steps in when the value is really missing.
Default parameters behave like ?? in one way. function play(volume = 10) only uses 10 when volume is undefined, so passing 0 keeps 0.
The twist: ??=
There is also ??=. It only sets the variable if it is currently null or undefined. That makes it a neat way to fill in default settings.
let theme = null;
theme ??= "dark";
console.log(theme); // prints: dark
let size = 0;
size ??= 12;
console.log(size); // prints: 0
theme was empty, so it became "dark". size was 0, which is a real value, so it was left alone. There is a matching ||= that fills in any falsy value.
Rule of thumb
If zero or an empty string is a real answer, use ?? for the backup.
You can't mix ?? with || or && without parentheses, so a || b ?? c is a SyntaxError. Add parentheses to say what you mean, like (a || b) ?? c.
Check yourself
const score = 0; score ?? 5 give??? only uses the backup for null or undefined. 0 is a real value, so it is kept.