---
title: "08 — Operators — Precedence, Short-Circuiting, and the Bitwise Oddities"
uid: expressions-and-operators
tags: ["bitwise", "comparison", "roadmap:javascript", "arithmetic", "ternary", "logical", "operators", "javascript"]
excerpt: "Operators are just functions with weird syntax; what matters is precedence (who binds tighter) and short-circuit evaluation (&& and || return operands, not booleans)."
date: 2026-08-13T03:28:06+0000
source: https://www.aveshina.my.id/en/blog/expressions-and-operators
---

"Plus, minus, and equals" was my operator inventory, and it skipped the two ideas that actually matter. The idea that everything else hangs off: **operators are just functions with weird syntax, and the two things that actually matter are precedence (which operator binds tighter) and the fact that && / || return one of their operands, not a strict boolean.** [1]

The framing that finally landed is grouping operators by what they *do*, then learning the two cross-cutting rules — precedence and short-circuiting — that govern all of them.

## The families

JavaScript has a handful of operator families, each with predictable members [1][2]:

- **Arithmetic** — +, -, *, /, % (remainder), ** (exponentiation). The + overload is the famous one: with a string operand it concatenates, otherwise it adds.
- **Comparison** — >, <, >=, <=, ==, ===, !=, !==. The strict variants (===, !==) never coerce; the loose ones do.
- **Logical** — && (AND), || (OR), ! (NOT). The trap: && and || don't return true/false, they return one of their operands.
- **Assignment** — =, plus compound forms +=, -=, *=, ||=, &&=, ??=. The compound forms combine an operation with assignment.
- **Bitwise** — &, |, ^, ~, <<, >>, >>>. Treat operands as 32-bit integers and operate bit-by-bit.
- **Unary** — +x (to number), -x (negate), ++/-- (increment/decrement), typeof, !, delete.
- **Ternary** — condition ? a : b, the only three-operand operator. A concise if/else for expressions.
- **Comma** — , evaluates both operands left-to-right and returns the last. Rare except in for loop headers.

## Precedence and associativity

Precedence decides which operator wins when several appear together: 2 + 3 * 4 is 14, not 20, because * binds tighter than + [3]. Associativity decides the order for equal-precedence operators: most are left-to-right (10 - 3 - 2 is 5), but assignment and exponentiation are right-to-left (2 ** 3 ** 2 is 512, not 64).

The practical rule: don't memorize the table — use parentheses. (2 + 3) * 4 is unambiguous and reads the same to the next person. When a line has three operators without parentheses, it's a bug waiting for someone to misread it.

## The logical-operator trap

This is the part that took me longest to internalize. && and || in JavaScript **do not return booleans** — they return one of their two operands, picked by short-circuit evaluation [4]:

- **a && b** — evaluates a. If a is falsy, returns a (short-circuits, b never runs). If a is truthy, returns b.
- **a || b** — evaluates a. If a is truthy, returns a (short-circuits). If a is falsy, returns b.

That's why the default-value pattern works:

```
const name = user.name || "Anonymous"; // if user.name is falsy, use "Anonymous"
const value = config?.enabled && config.value; // if enabled is truthy, take config.value
```

The operands don't have to be booleans. 0 || "fallback" returns "fallback"; "hello" && 42 returns 42. The *result* of &&/|| is always one of the operands verbatim, not a coerced boolean.

The newer **nullish coalescing operator ??** is the stricter cousin of ||: it only falls through on null or undefined, not on every falsy value. 0 ?? "fallback" is 0 (zero is a real value, keep it), where 0 || "fallback" would have replaced it. For default-value logic where 0, "", or false are legitimate inputs, ?? is the correct choice.

## The ternary operator

condition ? valueIfTrue : valueIfFalse is an *expression*, not a statement — it produces a value [5]. That makes it the right tool for conditional assignment and JSX rendering, and the wrong tool for branching side-effects (use if there). Deep nesting (a ? b : c ? d : e) is a readability trap; flatten it with early returns or a lookup table.

## Bitwise operators: the niche corner

Bitwise operators treat operands as 32-bit integers and work at the bit level [6]. & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (sign-propagating right shift), >>> (zero-fill right shift). They show up in flag manipulation, permission bitmasks, certain algorithm tricks (x >> 1 as a fast Math.floor(x/2) for positives), and some legacy graphics code. For most application code they're a curiosity — useful when needed, but I've gone years without writing one outside of a flags enum.

## How I use this

Three habits fall out. First, **parenthesize generously** — every expression with three or more operators gets parens, no exceptions, because the next reader (including future me) shouldn't have to recall precedence. Second, **?? for defaults over ||** whenever 0/""/false are valid values — it's the precise tool. Third, **ternary for values, if for side-effects** — and never nest a ternary deeper than one level. The operator families themselves I rarely think about; the cross-cutting rules (precedence, short-circuit) are what actually cause or prevent bugs.

## References

[1] Mozilla, "Expressions and operators," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators)

[2] I. Kantor, "Basic operators, maths," The Modern JavaScript Tutorial, 2024. [Online]. Available: [https://javascript.info/operators](https://javascript.info/operators)

[3] Mozilla, "Operator precedence," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence)

[4] Mozilla, "Logical operators (&&, ||, !)," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_logical_operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_logical_operators)

[5] Mozilla, "Conditional (ternary) operator," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#conditional_operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#conditional_operator)

[6] Mozilla, "Bitwise operators," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators)

```quiz
Q: What does `0 || "fallback"` return, and why?
- "fallback", because || returns the right operand
- "fallback", because 0 is falsy so || short-circuits to the right operand
correct: 1
explain: || evaluates the left operand; if truthy it returns it, if falsy it returns the right operand. 0 is falsy, so the result is "fallback". The key insight: || returns an operand, not a boolean.

Q: When is ?? preferable to || for a default value?
- When 0, "", or false are legitimate values that should NOT be replaced
- Never — they are identical
correct: 0
explain: ?? only falls through on null or undefined, so 0 ?? "x" is 0. || treats 0, "", false, NaN as falsy and would replace them. Use ?? when those falsy values are valid.

Q: `2 ** 3 ** 2` evaluates to 512, not 64. Why?
- Exponentiation is right-associative, so it's parsed as 2 ** (3 ** 2) = 2 ** 9 = 512
- It's left-associative like most operators
correct: 0
explain: ** is one of the few right-associative operators. The expression is 2 ** (3 ** 2). When in doubt, add parentheses to make intent explicit.

Q: The ternary operator `condition ? a : b` is best used for…
- producing a value in an expression (e.g., conditional assignment, JSX)
- branching side-effects across many statements
correct: 0
explain: The ternary is an expression that yields a value. For multi-statement branches or side-effects, use if/else. Deeply nested ternaries are a readability trap.

Q: Bitwise operators treat operands as…
- 32-bit integers and operate bit-by-bit
- IEEE 754 doubles and operate bit-by-bit
correct: 0
explain: Bitwise operators coerce operands to 32-bit signed integers, perform the bit operation, then return a number. They're niche — flags, permission masks, certain algorithm tricks — and rarely seen in application code.
```
