skip to content

Why is `const x = a ?? b || c;` a SyntaxError in JavaScript, and how do you express either intended grouping?

level: middleimportance: should knowfreq 38%

answer

  1. the parser refuses, it is not a runtime issue
  2. two different fallback tests in one expression
  3. the committee made you say which you meant
  4. parentheses restore both readings
  5. chaining with itself is still allowed

basics

~20 s

JavaScript forbids combining ?? with || or && in one expression without parentheses, so the parser rejects it. Write (a ?? b) || c or a ?? (b || c) to state the grouping explicitly; the two mean different things.

solid answer

~50 s

The grammar deliberately makes an unparenthesised mix of `??` with `||` or `&&` a SyntaxError. The two operators have different fallback rules — falsy versus nullish — so any implicit precedence would silently pick a meaning the reader might not intend, and the committee chose to force the author to be explicit instead. You fix it with parentheses: `(a ?? b) || c` first substitutes a nullish `a`, then applies a falsy check to the result, while `a ?? (b || c)` keeps `a` whenever it is non-nullish and otherwise picks between `b` and `c` by truthiness. Note the restriction is only against `&&` and `||`; `??` chains freely with itself (`a ?? b ?? c`), binds looser than arithmetic and comparison, and binds tighter than the conditional operator, so `a ?? b ? c : d` parses as `(a ?? b) ? c : d`.

code

javascript · 9 lines
javascript
const v = 0;

// const bad = v ?? 10 || 'fallback'; // SyntaxError: cannot mix ?? with ||

console.log((v ?? 10) || 'fallback'); // 'fallback' — 0 survives ??, then fails ||
console.log(v ?? (10 || 'fallback')); // 0 — v is not nullish, right side unevaluated

console.log(null ?? undefined ?? 'x'); // 'x' — chaining ?? with itself is legal
console.log(0 ?? 5 ? 'yes' : 'no');    // 'no' — parses as (0 ?? 5) ? ... : ...

go deeper

for a junior

Recognise the error message and know the fix: add parentheses around either a ?? b or b || c, because JavaScript will not let you mix ?? with || or && unparenthesised.

for a middle

Explain why the grammar forbids it — the falsy and nullish tests differ — and walk a value like 0 through both groupings to show they produce different results.

for a senior

Treat the parenthesisation as a design decision rather than a syntax chore: state explicitly which values in that expression count as missing, and prefer normalising the option at the boundary over stacking two fallback rules inline.

for a principal

Be able to discuss how a language committee handles ambiguous precedence generally — erroring rather than guessing keeps a new operator from silently changing the meaning of code, a tradeoff worth citing when you set conventions for your own APIs and DSLs.

## The rule In the ECMAScript grammar, the production for `??` is written so that its operands cannot themselves be an unparenthesised logical AND or OR expression. The practical consequence is a hard parse error: ```js // All SyntaxError: a ?? b || c; a || b ?? c; a && b ?? c; a ?? b && c; ``` This is a *syntax* error, not a runtime one — the whole script or module fails to parse, so nothing in the file runs. ## Why the language refuses to guess `||` returns its right operand for any falsy left operand; `??` does so only for `null` and `undefined`. Because the two tests are genuinely different, either precedence choice would produce an expression whose meaning a reader is likely to get wrong. Consider `a ?? b || c` with `a = 0`: - as `(a ?? b) || c`: `0 ?? b` is `0`, then `0 || c` is `c` - as `a ?? (b || c)`: `0` is not nullish, so the result is `0` One grouping keeps the zero and the other throws it away. Rather than have half the readership assume the wrong one, the grammar requires parentheses. ## The two fixes and what they mean ```js const v = 0; console.log((v ?? 10) || 'fallback'); // 'fallback' console.log(v ?? (10 || 'fallback')); // 0 // console.log(v ?? 10 || 'fallback'); // SyntaxError ``` `(a ?? b) || c` is "resolve absence first, then treat the result's falsiness as a second failure mode". `a ?? (b || c)` is "if `a` is present use it; otherwise compute a default that itself has a falsy fallback". In real code the second shape is the more common intent, e.g. `config.title ?? (envTitle || 'Untitled')`. ## What is allowed The restriction is narrow — it applies only to mixing with `&&` and `||`. ```js a ?? b ?? c; // fine: ?? is left-associative and chains x.y?.z ?? 'default'; // fine: optional chaining composes freely a ?? b + 1; // fine: parses as a ?? (b + 1) a ?? b === c; // fine: parses as a ?? (b === c) cond ? a ?? b : c; // fine a ?? b ? c : d; // fine: parses as (a ?? b) ? c : d ``` Arithmetic, comparison and equality all bind tighter than `??`, so they group on the right-hand side as you would expect. The conditional (ternary) operator binds *looser*, so an unparenthesised `??` on the left of a `?` becomes the condition. That last one is worth reading twice, because it is easy to misread `a ?? b ? c : d` as `a ?? (b ? c : d)` — it is not. ## Interaction with logical assignment The same intent shows up in the assignment forms: `x ??= y` and `x ||= y` are separate operators, and there is no way to mix their tests in one statement either. If you need both rules, write the two steps out. ## Interaction with tooling Because this is a parse error, it fails loudly and early — you will see it the first time the file is loaded, not on some rare code path. That makes it one of the friendlier errors in the language: the fix is always mechanical, and choosing the parentheses forces you to answer the real question of whether zero and the empty string should count as missing. ## What to say in an interview Name the rule (mixing `??` with `&&`/`||` unparenthesised is a SyntaxError), explain the motivation (the two operators test different things, so implicit precedence would mislead), and show that you know the two groupings are not equivalent by walking a falsy-but-not-nullish example such as `0` through both.

  • How does `??` bind relative to the conditional operator?
    Tighter. `a ?? b ? c : d` parses as `(a ?? b) ? c : d`, so the coalesced value becomes the condition. That is a common misreading, since people expect the ternary to be the right operand of `??`. If you want that, parenthesise: `a ?? (b ? c : d)`.
  • Is `a ?? b ?? c` legal, and how does it evaluate?
    Legal and left-associative: it behaves as `(a ?? b) ?? c`, returning the first operand that is neither null nor undefined, and short-circuiting so later operands are not evaluated once one is found. The restriction only forbids mixing `??` with `&&` or `||` without parentheses.
  • Why did the language choose an error rather than assigning a precedence?
    Because the operators test different conditions — falsy versus nullish — the two possible groupings can give different results for values like `0` and `''`. Any silent precedence would leave a large fraction of readers assuming the other one, so the grammar makes the author write the parentheses and commit to a meaning.

saying these in an interview costs you the question

  • Thinks it is a runtime TypeError rather than a parse error
  • Says ?? simply has lower precedence than || so it just works
  • Claims the two parenthesised forms are equivalent
  • Believes ?? cannot be chained with itself either
  • Reads a ?? b ? c : d as a ?? (b ? c : d)

context