JavaScript Operator Precedence and Associativity: A Developer's Complete Guide

JavaScript Operator Precedence and Associativity

As JavaScript developers, we write expressions every day without thinking twice about how the JavaScript engine evaluates them. But understanding operator precedence and associativity is crucial for writing predictable, bug-free code and avoiding those head-scratching moments when your code doesn’t behave as expected.

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.


What Are Operator Precedence and Associativity?

Operator precedence determines which operations are performed first in expressions with multiple operators. Think of it like the mathematical order of operations (PEMDAS) you learned in school.

Associativity determines the order of evaluation when operators have the same precedence level. It can be either left-to-right or right-to-left.

// Without understanding precedence, this might be confusing:
const result = 5 + 3 * 2; // Is this 16 or 11?
console.log(result); // 11 (multiplication happens first)

The Precedence Table: What You Need to Know

Here are the most commonly used operators ranked by precedence (lower numbers = higher precedence):

Operator Precedence Associativity Example
() (grouping) 1 n/a (a + b)
?. (optional chaining) 2 Left-to-right obj?.prop
++, -- (postfix) 3 n/a a++
?: (ternary) 4 Right-to-left a ? b : c
?? (nullish coalescing) 5 Left-to-right a ?? b
===, !==, ==, != 6 Left-to-right a === b
&& (logical AND) 7 Left-to-right a && b
|| (logical OR) 8 Left-to-right a || b
+, - (arithmetic) 13 Left-to-right a + b
*, /, % 14 Left-to-right a * b
typeof, ! (unary) 17 Right-to-left typeof a
= (assignment) 18 Right-to-left a = b

Note: This is a simplified table. The complete MDN precedence table has 20 levels.

Left-to-Right Associativity Examples

When operators have left-to-right associativity, the JavaScript engine evaluates expressions from left to right when multiple operators of the same precedence appear together. This behavior is intuitive for most arithmetic operations but becomes crucial when working with newer operators like nullish coalescing.

Nullish Coalescing (??)

The nullish coalescing operator is particularly useful for providing fallback values, and its left-to-right associativity allows you to chain multiple fallbacks elegantly. When you chain multiple ?? operators, each one is evaluated from left to right, only moving to the next option if the current value is null or undefined.

let user = null;
let guest = undefined;
let defaultName = "Anonymous";

// Evaluates as: ((user ?? guest) ?? defaultName)
const name = user ?? guest ?? defaultName;
console.log(name); // "Anonymous"

// Step by step:
// 1. user ?? guest → null ?? undefined → undefined
// 2. undefined ?? defaultName → undefined ?? "Anonymous" → "Anonymous"

Arithmetic Operations

Arithmetic operators follow the familiar left-to-right evaluation pattern. This means that when you have multiple addition and subtraction operations (or multiplication and division), they’re processed sequentially from left to right. Understanding this is essential for avoiding calculation errors in complex mathematical expressions.

// Evaluates as: ((10 - 3) + 2)
const result = 10 - 3 + 2;
console.log(result); // 9

// Without parentheses, left-to-right:
// 1. 10 - 3 → 7
// 2. 7 + 2 → 9

// Compare with forced right-to-left:
const result2 = 10 - (3 + 2);
console.log(result2); // 5

Logical AND Chain

Logical AND operations are commonly chained in conditional statements and guards. The left-to-right associativity means that if any condition in the chain fails (returns a falsy value), the evaluation stops immediately—a behavior known as short-circuiting. This makes logical AND chains both efficient and predictable.

const user = { isActive: true, hasPermission: true, isVerified: true };

// Evaluates as: (((user.isActive && user.hasPermission) && user.isVerified) && "Access granted")
const access = user.isActive && user.hasPermission && user.isVerified && "Access granted";
console.log(access); // "Access granted"

Right-to-Left Associativity Examples

Right-to-left associativity is less intuitive but appears in several important JavaScript operators. Understanding this concept is crucial for assignment operations, ternary operators, and unary operations. The key insight is that these operations build up from the rightmost part of the expression first.

Assignment Operations

Assignment operators work right-to-left, which enables the common pattern of assigning the same value to multiple variables in a single statement. This associativity means the rightmost assignment happens first, and its result becomes the value for the next assignment to the left.

let a, b, c;

// Evaluates as: a = (b = (c = 42))
a = b = c = 42;
console.log(a, b, c); // 42 42 42

// Step by step:
// 1. c = 42 → c gets value 42, returns 42
// 2. b = 42 → b gets value 42, returns 42  
// 3. a = 42 → a gets value 42, returns 42

Ternary Operator Chains

The ternary operator’s right-to-left associativity allows you to chain multiple conditions without parentheses. This creates a natural “if-else if-else” structure that reads intuitively, even though the evaluation happens from right to left. This pattern is commonly used for grade calculations, status assignments, and multi-tier conditional logic.

const score = 85;
const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'F';

// This is evaluated as:
// score >= 90 ? 'A' : (score >= 80 ? 'B' : (score >= 70 ? 'C' : 'F'))
console.log(grade); // 'B'

Typeof Operator

Unary operators like typeof have right-to-left associativity, which becomes apparent when you chain them together. While chaining typeof operators isn’t common in production code, understanding this behavior helps you predict how complex expressions with multiple unary operators will be evaluated.

const x = 42;

// Evaluates as: typeof (typeof x)
const result = typeof typeof x;
console.log(result); // "string"

// Step by step:
// 1. typeof x → typeof 42 → "number"
// 2. typeof "number" → "string"

Common Pitfalls and How to Avoid Them

Even experienced developers can fall into precedence traps, especially when mixing different types of operators or when working with newer JavaScript features. These pitfalls often lead to subtle bugs that are hard to debug because the code “looks right” but doesn’t behave as expected. Let’s examine the most common scenarios and learn how to write defensive code.

Mixing Logical and Comparison Operators

One of the most frequent sources of confusion occurs when mixing logical operators (&&, ||) with comparison operators. The precedence rules can make expressions ambiguous, leading to logic errors that only surface under specific conditions.

// ❌ Confusing precedence
const isValid = user && user.age >= 18 && user.hasAccount;

// ✅ Better with explicit parentheses
const isValid = user && (user.age >= 18) && user.hasAccount;

Ternary with Nullish Coalescing

The interaction between ternary operators and nullish coalescing can be tricky because both are relatively new additions to JavaScript’s operator set. Their precedence relationship isn’t always intuitive, and mixing them without understanding their evaluation order can lead to unexpected results.

const user = null;
const displayName = user?.name ?? 'Guest';

// This works as expected because ?. has higher precedence than ??
// Evaluates as: (user?.name) ?? 'Guest'
console.log(displayName); // 'Guest'

// But be careful with more complex expressions:
const result = condition ? value ?? 'default' : 'other';
// This is: condition ? (value ?? 'default') : 'other'

Assignment in Conditionals

A classic mistake that catches even seasoned developers is accidentally using assignment (=) instead of comparison (=== or ==) in conditional statements. This error is particularly dangerous because it doesn’t throw an error—instead, it silently changes your program’s behavior.

let user;

// ❌ This assigns, doesn't compare!
if (user = getCurrentUser()) {
    console.log('User found');
}

// ✅ Use comparison or make assignment explicit
if ((user = getCurrentUser())) {
    console.log('User found');
}
// or better:
user = getCurrentUser();
if (user) {
    console.log('User found');
}

Real-World Applications

Understanding operator precedence isn’t just academic—it directly impacts how you write clean, maintainable code in real projects. Let’s look at practical scenarios where precedence knowledge helps you write better code and avoid common bugs that can slip through code reviews.

API Response Handling

When working with API responses, you often need to safely navigate nested objects and provide fallback values. Proper understanding of precedence allows you to write concise yet safe code that handles various edge cases without verbose null checks.

// Clean chaining with proper precedence understanding
const processUser = (response) => {
    const user = response?.data?.user ?? {};
    const name = user.firstName ?? user.username ?? 'Anonymous';
    const isActive = user.status === 'active' && user.verified === true;
    
    return {
        name,
        isActive,
        displayName: isActive ? name : `${name} (inactive)`
    };
};

Form Validation

Form validation logic often involves complex boolean expressions that combine multiple conditions. Understanding precedence helps you write validation functions that are both readable and logically correct, avoiding the need for excessive parentheses while maintaining clarity.

const validateForm = (data) => {
    // Precedence makes this readable without extra parentheses
    const isValidEmail = data.email && typeof data.email === 'string' && data.email.includes('@');
    const isValidAge = data.age >= 13 && data.age <= 120;
    const hasConsent = data.consent === true || data.parentalConsent === true;
    
    return isValidEmail && isValidAge && hasConsent;
};

Best Practices for Production Code

Writing production-ready code means balancing conciseness with readability. While understanding precedence allows you to write more compact expressions, the primary goal should always be code that other developers (including your future self) can easily understand and maintain. Here are the key principles to follow.

1. Use Parentheses for Clarity

Even when you know the precedence rules, explicit parentheses make your intentions clear to other developers. This is especially important in team environments where not everyone may have the same level of precedence knowledge.

// ❌ Relies on precedence knowledge
const result = a && b || c && d;

// ✅ Explicit and clear
const result = (a && b) || (c && d);

2. Break Complex Expressions

Long expressions that rely heavily on precedence knowledge are maintenance nightmares. Breaking them into smaller, named variables makes your code self-documenting and much easier to debug when things go wrong.

// ❌ Hard to debug
const isAuthorized = user?.permissions?.includes('admin') ?? false && user.isActive && Date.now() < user.sessionExpiry;

// ✅ Easier to understand and debug
const hasAdminPermission = user?.permissions?.includes('admin') ?? false;
const isSessionValid = user.isActive && Date.now() < user.sessionExpiry;
const isAuthorized = hasAdminPermission && isSessionValid;

3. Consistent Formatting

Consistent formatting and spacing in complex expressions helps readers parse the logic more easily. This is especially important when dealing with ternary operators and chained logical operations.

// ✅ Consistent spacing helps readability
const result = condition
    ? longVariableName ?? defaultValue
    : alternativeValue;

Testing Your Understanding

Now that we’ve covered the theory and practical applications, let’s test your grasp of operator precedence and associativity. Try to work through these examples step by step, considering both precedence and associativity rules. Don’t just guess—think through each operation’s evaluation order.

Try to predict the output of these expressions before running them:

// Challenge 1
console.log(false || true && false);

// Challenge 2  
let x;
console.log(x = 5 + 3 * 2);

// Challenge 3
console.log(null ?? undefined ?? 0 || 'fallback');
Answers
  1. false (true && false = false, false || false = false)
  2. 11 (3 * 2 = 6, 5 + 6 = 11, x = 11)
  3. 0 (null ?? undefined = undefined, undefined ?? 0 = 0, 0 || ‘fallback’ = ‘fallback’, but ?? has higher precedence than ||, so it’s (null ?? undefined ?? 0) || ‘fallback’ = 0 || ‘fallback’ = ‘fallback’. Actually, this evaluates to 'fallback')

Tools and Resources

Modern development tools can help you write better code and catch precedence-related issues before they become bugs. Here are the most effective tools and practices for managing operator complexity in your JavaScript projects.

  • ESLint rules: Enable no-mixed-operators to catch potentially confusing precedence
  • Prettier: Automatically formats complex expressions for better readability
  • TypeScript: Helps catch type-related precedence issues at compile time
  • Browser DevTools: Use the console to test precedence behavior interactively

Conclusion

Understanding operator precedence and associativity isn’t about memorizing tables—it’s about writing code that’s predictable, maintainable, and less prone to bugs. When in doubt, use parentheses to make your intentions explicit. Your future self (and your teammates) will thank you.

The key takeaway? Explicit is better than implicit. Clear code that doesn’t rely on deep precedence knowledge is more valuable than clever one-liners that might confuse other developers.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to Use JavaScript setTimeout()
How to Use JavaScript setTimeout()

How to Remove Elements from a JavaScript Array
How to Remove Elements from a JavaScript Array

How to check if an array is empty in JavaScript?
How to check if an array is empty in JavaScript?

How to convert a string to boolean in JavaScript
How to convert a string to boolean in JavaScript

How to change opacity on hover in CSS
How to change opacity on hover in CSS

How to replace all occurrences of a string in JavaScript?
How to replace all occurrences of a string in JavaScript?