Understanding Operator Precedence in JavaScript: Why Parentheses Matter with `??` and `?:`

When writing JavaScript or TypeScript, small syntax choices can cause big logic bugs—especially when dealing with operator precedence. One such case arises when using the nullish coalescing operator ??
together with the ternary conditional operator ?:
.
Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.
Let’s explore a deceptively simple line of code and the bug it hides.
The Code
Here’s a common pattern when working with form options or dropdowns:
const value = option.value ?? (typeof option === 'string' ? option : option.label)
This line is perfectly valid and behaves as expected:
-
If
option.value
is defined (i.e., notnull
orundefined
), it will be used. -
Otherwise, we check if
option
is a string.- If it is, return the string itself.
- If not, fall back to
option.label
.
Now compare that to this subtly different version:
const value = option.value ?? typeof option === 'string' ? option : option.label
Looks almost identical, right? Unfortunately, this one is buggy.
What’s the Difference?
The difference is operator precedence—how JavaScript decides which part of the expression to evaluate first.
In the second version (without parentheses), the code is interpreted as:
const value = (option.value ?? typeof option) === 'string' ? option : option.label
This completely changes the logic:
- It tries to evaluate
option.value ?? typeof option
. - Then compares the result of that to the string
'string'
. - Based on whether that comparison is true or false, it returns
option
oroption.label
.
This is likely not what the original code intended.
Why This Happens: Operator Precedence Explained
Let’s break it down:
Operator | Precedence | Associativity |
---|---|---|
?: (ternary conditional) |
4 | Right-to-left |
?? (nullish coalescing) |
5 | Left-to-right |
=== , !== (equality checks) |
6 | Left-to-right |
typeof |
17 | Right-to-left |
Because ??
has lower precedence than ?:
, the ternary operator tries to take over unless you explicitly wrap your fallback logic in parentheses.
How to Fix It
Always use parentheses to make your intent clear when combining ??
and ?:
:
✅ Correct:
const value = option.value ?? (typeof option === 'string' ? option : option.label)
❌ Incorrect:
const value = option.value ?? typeof option === 'string' ? option : option.label
This isn’t just about syntax—it’s about preventing silent logic bugs that are hard to detect during review or testing.
Real-World Example
This pattern is especially common when handling UI components, like dropdowns, where options can be strings, objects, or nested structures. Here’s an example from a custom Autocomplete component:
type Option = string | { label: string; value?: string }
const getOptionValue = (option: Option) =>
typeof option === 'string'
? option
: option.value ?? option.label
This kind of function is simple, readable, and safe—because we’re explicit about precedence.
Final Thoughts
Understanding operator precedence isn’t about memorizing a table—it’s about developing instincts for ambiguity. When in doubt:
- Add parentheses to group your logic.
- Refactor into small helper functions for clarity.
- Write tests for fallback logic—especially when using
??
,||
, and?:
.