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

Operator Precedence in JavaScript

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 ?:.

Table of Contents

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., not null or undefined), 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:

  1. It tries to evaluate option.value ?? typeof option.
  2. Then compares the result of that to the string 'string'.
  3. Based on whether that comparison is true or false, it returns option or option.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 ?:.

About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.