08 — Operators — Precedence, Short-Circuiting, and the Bitwise Oddities
"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.valueThe 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
[2] I. Kantor, "Basic operators, maths," The Modern JavaScript Tutorial, 2024. [Online]. Available: 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
[4] Mozilla, "Logical operators (&&, ||, !)," MDN Web Docs, 2024. [Online]. Available: 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
[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
Knowledge check · Question 1 of 5
What does `0 || "fallback"` return, and why?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!