JavaScript Operator Precedence Without Guessing
JavaScript operator precedence decides which parts of an expression run first, but memorizing the whole table is the least useful way to write clearer code. The practical skill is knowing the few places precedence causes real bugs, then using parentheses, named variables, and smaller expressions so nobody has to guess.
If a reader needs a chart to understand your line, the line is probably doing too much.
Table of contents
- The short rule
- Parentheses are documentation
- Arithmetic precedence
- String plus still happens left to right
- Comparisons happen before logical decisions
- Logical AND runs before logical OR
- Nullish coalescing requires grouping
- Ternaries have low precedence
- Assignment belongs at the edge
- The exponent operator has one sharp edge
- A no-guessing checklist
- Key Takeaways
The short rule
Operator precedence is JavaScript's rulebook for grouping expressions before evaluation.
console.log(2 + 3 * 4); // 14
console.log((2 + 3) * 4); // 20
JavaScript does multiplication before addition, so the first expression is grouped like this:
2 + (3 * 4);
Parentheses override that grouping. They do not make the code slower in any meaningful way for normal application code. They make the intent visible, which is usually the more expensive problem.
Parentheses are documentation
This line is legal:
const finalTotal = subtotal - discount + shipping * taxRate;
But a reader has to ask a tedious question: should shipping be multiplied by the tax rate, or should the whole shipped amount be taxed?
If the business rule is "apply the discount, add shipping, then tax the result," write that rule:
const discountedTotal = subtotal - discount;
const shippedTotal = discountedTotal + shipping;
const finalTotal = shippedTotal * taxRate;
Or, if the expression is still small:
const finalTotal = (subtotal - discount + shipping) * taxRate;
Parentheses are not a confession that you forgot the precedence table. They are a courtesy to the next person reading the code. Sometimes that person is you after lunch.
Arithmetic precedence
The arithmetic rules are the ones most learners expect:
| Operators | Runs before |
|---|---|
** | *, /, %, +, - |
*, /, % | +, - |
+, - | Comparisons, logical operators, assignment |
That means this average is wrong:
const clarity = 8;
const usefulness = 10;
const pacing = 6;
const averageScore = clarity + usefulness + pacing / 3;
console.log(averageScore); // 20
Only pacing is divided by 3. The intended average needs the sum first:
const averageScore = (clarity + usefulness + pacing) / 3;
console.log(averageScore); // 8
This is the most common precedence bug because the answer can look plausible. A wrong average of 20 is obvious here. A wrong money calculation in a larger expression can sit there quietly, which is rude behavior from arithmetic.
String plus still happens left to right
Precedence is not the only rule. Operators with the same precedence usually group left to right.
console.log(1 + 2 + "3"); // "33"
console.log("1" + 2 + 3); // "123"
Both expressions use +, so JavaScript evaluates from left to right.
In the first line:
(1 + 2) + "3";
// 3 + "3" -> "33"
In the second line:
("1" + 2) + 3;
// "12" + 3 -> "123"
This is not a reason to memorize more rules. It is a reason to avoid mixing numeric addition and string concatenation in the same expression.
const total = Number(cartCount) + bonusItems;
const label = `Items: ${total}`;
Separate the numeric work from the display text. Your code gets less slippery immediately.
Comparisons happen before logical decisions
Comparisons produce booleans:
const age = 16;
const hasGuardianApproval = true;
const canAccessWorkshop = age >= 13 && hasGuardianApproval === true;
console.log(canAccessWorkshop); // true
JavaScript groups the comparisons before combining them with &&:
(age >= 13) && (hasGuardianApproval === true);
That is useful, but do not rely on readers to unpack a dense permission rule:
const canAccess =
age >= 13 && country === "CA" || hasInvite && !isSuspended;
That line may be correct. It may also be a support ticket wearing a jacket.
Name the pieces:
const meetsAgeRule = age >= 13;
const isInAllowedCountry = country === "CA";
const hasValidInvite = hasInvite && !isSuspended;
const canAccess = (meetsAgeRule && isInAllowedCountry) || hasValidInvite;
Now the grouping is not a puzzle. It is the rule.
Logical AND runs before logical OR
&& has higher precedence than ||.
console.log(true || false && false); // true
JavaScript groups it like this:
true || (false && false);
That is often surprising because humans read the line as one sentence. In real code, mixed logical operators deserve parentheses:
const canEdit = isAdmin || (isOwner && isPublished);
Maybe that is the rule. Maybe the rule is different:
const canEdit = (isAdmin || isOwner) && isPublished;
Those two expressions are not equivalent:
const isAdmin = true;
const isOwner = false;
const isPublished = false;
console.log(isAdmin || (isOwner && isPublished)); // true
console.log((isAdmin || isOwner) && isPublished); // false
Parentheses are the difference between "admins can always edit" and "nobody can edit unpublished content unless some other rule exists." That is not punctuation trivia. That is product behavior.
Nullish coalescing requires grouping
The nullish coalescing operator, ??, falls back only when the left side is null or undefined.
const savedLimit = 0;
const limit = savedLimit ?? 25;
console.log(limit); // 0
JavaScript does not allow ?? to be mixed directly with || or && without parentheses:
const label = username || nickname ?? "Guest";
// SyntaxError
Group the intended decision:
const label = (username || nickname) ?? "Guest";
Or, more often, choose one fallback rule and keep it consistent:
const displayName = username ?? nickname ?? "Guest";
Use ?? when empty strings, 0, or false are valid values. Use || only when every falsy value should fall back. The Truthy and Falsy Values article covers that distinction in more detail.
Ternaries have low precedence
The ternary operator chooses between two values:
const label = isActive ? "Active" : "Inactive";
It has lower precedence than comparison and logical operators, so this works the way many people expect:
const age = 20;
const hasPass = true;
const label = age >= 18 && hasPass ? "Admit" : "Deny";
console.log(label); // "Admit"
JavaScript treats the condition like this:
(age >= 18 && hasPass) ? "Admit" : "Deny";
Still, add parentheses when the condition has real logic:
const label = (age >= 18 && hasPass) ? "Admit" : "Deny";
That is especially useful when a ternary is inside a template literal:
const message = `Status: ${isActive ? "active" : "inactive"}`;
Keep ternaries small. If the condition or branches need several operators, use named variables or an if statement. The JavaScript Ternary Operator guide covers that judgement call.
Assignment belongs at the edge
Assignment has low precedence and returns the assigned value:
let count = 0;
console.log((count = 5)); // 5
console.log(count); // 5
That is why accidental assignment inside a condition is so painful:
let role = "viewer";
if ((role = "admin")) {
console.log("Access granted");
}
This logs "Access granted" because "admin" is truthy. The variable was assigned, not compared.
The fix is not cleverness. Compare with === and keep assignment out of the condition:
if (role === "admin") {
console.log("Access granted");
}
When an expression both changes state and asks a question, split it:
const nextCount = count + 1;
const isOverLimit = nextCount > limit;
if (!isOverLimit) {
count = nextCount;
}
There are places where assignment expressions are idiomatic, but beginner application code is rarely improved by hiding a state change in the middle of a condition.
The exponent operator has one sharp edge
The exponent operator, **, has high precedence:
console.log(2 ** 3 * 4); // 32
That is grouped like this:
(2 ** 3) * 4;
The sharp edge is unary minus. JavaScript does not allow this:
-2 ** 2;
// SyntaxError
Choose the meaning with parentheses:
console.log(-(2 ** 2)); // -4
console.log((-2) ** 2); // 4
That is a perfect example of the whole rule for this article: when meaning could split, group it.
A no-guessing checklist
Before leaving a mixed expression in place, run it through this checklist:
| Expression shape | Safer habit |
|---|---|
| Arithmetic mixed with a final comparison | Name the numeric result, then compare it |
&& mixed with ` | |
?? near && or ` | |
| Ternary with a compound condition | Parenthesize the condition or name it |
| Assignment inside another expression | Split the state change onto its own line |
+ used with possible strings | Convert numbers first or use a template literal for display |
| Optional chaining plus fallback | Decide whether missing data is actually allowed |
Here is a messy expression:
const accessLabel =
age >= 13 && country === "CA" || inviteCode && !isSuspended
? username || "Guest"
: "Blocked";
Here is the same behavior without the guessing game:
const meetsAgeRule = age >= 13;
const isInAllowedCountry = country === "CA";
const hasUsableInvite = Boolean(inviteCode) && !isSuspended;
const canAccess = (meetsAgeRule && isInAllowedCountry) || hasUsableInvite;
const displayName = username || "Guest";
const accessLabel = canAccess ? displayName : "Blocked";
The second version is longer. Good. It gives each idea a name. Long code is not automatically better, but compressed logic is not automatically mature. Sometimes it is just hiding the part you need to review.
Key Takeaways
- JavaScript operator precedence is a grouping rule, not a readability strategy.
- Parentheses are useful even when JavaScript would already pick the same order.
- Mixed
&&and||expressions should show their intended grouping. - Use
??deliberately, especially when0,"", orfalseare valid values. - If an expression calculates, compares, chooses, and assigns in one line, split it before it starts charging rent.