JS Operators: Arithmetic, Logical & Comparison

Master all JavaScript operators including arithmetic, comparison, logical, assignment, and bitwise operators. Learn operator precedence, short-circuit evaluation, and practical patterns with real code examples.

JavaScriptbeginner
13 min read

Operators are the building blocks of every expression in JavaScript. They take values, perform operations, and produce results. From basic math (+, -, *) to logical gates (&&, ||, !) to comparisons (===, >, <), operators control how data flows through your code. This guide covers every operator category with practical examples, explains precedence rules that determine execution order, and highlights the patterns and pitfalls you will encounter in real projects.

Arithmetic Operators

Arithmetic operators perform mathematical calculations. JavaScript supports seven arithmetic operators:

javascriptjavascript
const a = 15;
const b = 4;
 
console.log(a + b);  // 19  (addition)
console.log(a - b);  // 11  (subtraction)
console.log(a * b);  // 60  (multiplication)
console.log(a / b);  // 3.75 (division, always returns float)
console.log(a % b);  // 3   (modulo/remainder)
console.log(a ** b); // 50625 (exponentiation, 15^4)
OperatorNameExampleResult
+Addition10 + 313
-Subtraction10 - 37
*Multiplication10 * 330
/Division10 / 33.333...
%Modulo (remainder)10 % 31
**Exponentiation10 ** 31000
+ (unary)Numeric conversion+"42"42
- (unary)Negation-42-42

Practical Arithmetic Patterns

javascriptjavascript
// Check if a number is even or odd
const num = 7;
const isEven = num % 2 === 0; // false (7 % 2 === 1)
 
// Calculate percentage
const total = 250;
const part = 75;
const percentage = (part / total) * 100; // 30
 
// Round to 2 decimal places
const price = 19.99 * 1.08; // 21.5892
const rounded = Math.round(price * 100) / 100; // 21.59
 
// Clamp a value between min and max
const value = 150;
const clamped = Math.min(Math.max(value, 0), 100); // 100

Increment and Decrement

The ++ and -- operators add or subtract 1. Their position (prefix vs postfix) determines when the change takes effect:

javascriptjavascript
let count = 5;
 
// Postfix: returns current value, THEN increments
console.log(count++); // 5 (returns 5, count becomes 6)
console.log(count);   // 6
 
// Prefix: increments FIRST, then returns new value
console.log(++count); // 7 (count becomes 7, returns 7)
console.log(count);   // 7
Avoid Increment in Expressions

Using ++ or -- inside complex expressions makes code hard to read. Prefer count += 1 for clarity. The postfix/prefix distinction is a common source of off-by-one bugs.

Comparison Operators

Comparison operators compare two values and return a boolean (true or false).

javascriptjavascript
console.log(5 > 3);   // true
console.log(5 < 3);   // false
console.log(5 >= 5);  // true
console.log(5 <= 4);  // false
console.log(5 === 5); // true (strict equality)
console.log(5 !== 3); // true (strict inequality)

Strict vs Loose Equality

This is one of the most important distinctions in JavaScript:

javascriptjavascript
// Strict equality (===): no type coercion
console.log(5 === 5);     // true
console.log(5 === "5");   // false (different types)
console.log(0 === false); // false (different types)
console.log(null === undefined); // false
 
// Loose equality (==): coerces types before comparing
console.log(5 == "5");    // true (string coerced to number)
console.log(0 == false);  // true (false coerced to 0)
console.log("" == false); // true (both coerce to 0)
console.log(null == undefined); // true (special rule)
Expression=== (Strict)== (Loose)Why They Differ
5 vs "5"falsetrueLoose coerces string to number
0 vs falsefalsetrueLoose coerces boolean to number
"" vs falsefalsetrueLoose coerces both to 0
null vs undefinedfalsetrueLoose treats them as equal
NaN vs NaNfalsefalseNaN is never equal to anything
[] vs falsefalsetrueLoose coerces array to 0
Always Use === in Production

Loose equality (==) applies a complex set of coercion rules that even experienced developers cannot always predict. Using === consistently eliminates an entire category of bugs. The ESLint eqeqeq rule enforces this automatically.

Comparing Strings

Strings are compared character by character using Unicode code points:

javascriptjavascript
console.log("apple" < "banana");  // true ("a" < "b" in Unicode)
console.log("abc" < "abd");      // true ("c" < "d")
console.log("10" < "9");         // true ("1" < "9" in Unicode, string comparison!)
console.log(10 < 9);             // false (numeric comparison)
 
// Case-insensitive comparison
const a = "Hello";
const b = "hello";
console.log(a.toLowerCase() === b.toLowerCase()); // true

Logical Operators

Logical operators combine or invert boolean expressions. JavaScript has three main logical operators plus the nullish coalescing operator.

AND (&&)

Returns the first falsy value, or the last value if all are truthy:

javascriptjavascript
console.log(true && true);     // true
console.log(true && false);    // false
console.log(false && true);    // false (short-circuits, doesn't evaluate right side)
 
// Short-circuit evaluation for conditional execution
const user = { name: "Ada", isAdmin: true };
user.isAdmin && console.log("Admin panel loaded"); // Logs the message
 
// Returns the actual value, not just true/false
console.log("hello" && 42);     // 42 (both truthy, returns last)
console.log("" && 42);          // "" (first is falsy, returns it)
console.log(null && "hello");   // null (first is falsy, returns it)

OR (||)

Returns the first truthy value, or the last value if all are falsy:

javascriptjavascript
console.log(true || false);    // true
console.log(false || true);    // true
console.log(false || false);   // false
 
// Default values pattern
const username = "" || "Anonymous";  // "Anonymous" ("" is falsy)
const count = 0 || 10;              // 10 (0 is falsy, PROBLEM if 0 is valid!)
 
// Returns the actual value
console.log("hello" || "world"); // "hello" (first is truthy)
console.log("" || "fallback");   // "fallback"
console.log(null || undefined || "last"); // "last"

Nullish Coalescing (??)

Returns the right side ONLY when the left side is null or undefined (not for other falsy values):

javascriptjavascript
console.log(0 ?? 10);          // 0 (0 is NOT null/undefined)
console.log("" ?? "fallback"); // "" (empty string is NOT null/undefined)
console.log(null ?? "default"); // "default"
console.log(undefined ?? 42);  // 42
 
// Compare with ||
const score1 = 0 || 100;   // 100 (wrong! 0 was a valid score)
const score2 = 0 ?? 100;   // 0 (correct! ?? only catches null/undefined)
 
const theme1 = "" || "dark";  // "dark" (wrong if "" means "no theme selected")
const theme2 = "" ?? "dark";  // "" (correct! ?? preserves empty string)

NOT (!)

Inverts a boolean value. Double-NOT (!!) converts any value to its boolean equivalent:

javascriptjavascript
console.log(!true);      // false
console.log(!false);     // true
console.log(!0);         // true (0 is falsy)
console.log(!"hello");   // false ("hello" is truthy)
console.log(!null);      // true
 
// Double-NOT for explicit boolean conversion
console.log(!!0);        // false
console.log(!!"hello");  // true
console.log(!!null);     // false
console.log(!!{});       // true

Assignment Operators

Assignment operators combine an operation with assignment in a single step:

javascriptjavascript
let x = 10;
 
x += 5;   // x = x + 5  → 15
x -= 3;   // x = x - 3  → 12
x *= 2;   // x = x * 2  → 24
x /= 4;   // x = x / 4  → 6
x %= 4;   // x = x % 4  → 2
x **= 3;  // x = x ** 3 → 8
 
// Logical assignment operators (ES2021)
let a = null;
a ??= "default"; // a = a ?? "default" → "default"
 
let b = "";
b ||= "fallback"; // b = b || "fallback" → "fallback"
 
let c = "existing";
c &&= "updated";  // c = c && "updated" → "updated"
OperatorEquivalentDescription
+=x = x + rightAdd and assign
-=x = x - rightSubtract and assign
*=x = x * rightMultiply and assign
/=x = x / rightDivide and assign
%=x = x % rightModulo and assign
**=x = x ** rightExponent and assign
??=x = x ?? rightNullish coalescing assign
||=x = x || rightLogical OR assign
&&=x = x && rightLogical AND assign

Operator Precedence

When multiple operators appear in one expression, precedence rules determine the order of evaluation. Higher precedence operators execute first.

javascriptjavascript
// Multiplication before addition (standard math order)
console.log(2 + 3 * 4);     // 14, not 20
console.log((2 + 3) * 4);   // 20 (parentheses override precedence)
 
// Comparison before logical AND
console.log(5 > 3 && 2 < 4); // true (comparisons run first, then &&)
 
// Logical AND before logical OR
console.log(true || false && false); // true (&& runs first: false && false = false, then true || false = true)
PrecedenceOperator(s)Category
Highest()Grouping
++, --, !, typeof, + (unary), - (unary)Unary
**Exponentiation
*, /, %Multiplicative
+, -Additive
<, >, <=, >=, instanceof, inRelational
===, !==, ==, !=Equality
&&Logical AND
||Logical OR
??Nullish coalescing
? :Ternary
Lowest=, +=, -=, etc.Assignment
When in Doubt, Use Parentheses

If you are unsure about precedence, add parentheses. They make the evaluation order explicit and improve readability. Writing (a > b) && (c < d) is clearer than a > b && c < d, even though they produce the same result.

Real-World Operator Patterns

Form Validation

javascriptjavascript
function validateRegistration(email, password, age) {
  const isEmailValid = email.includes("@") && email.includes(".");
  const isPasswordStrong = password.length >= 8 && /[A-Z]/.test(password) && /\d/.test(password);
  const isAgeValid = typeof age === "number" && age >= 13 && age <= 120;
 
  if (!isEmailValid) return { valid: false, error: "Invalid email format" };
  if (!isPasswordStrong) return { valid: false, error: "Password needs 8+ chars, uppercase, and digit" };
  if (!isAgeValid) return { valid: false, error: "Age must be 13-120" };
 
  return { valid: true, error: null };
}
 
const result = validateRegistration("ada@runehub.dev", "Str0ngPass", 25);
console.log(result); // { valid: true, error: null }

Safe Property Access Chain

javascriptjavascript
const config = {
  database: {
    host: "localhost",
    port: 5432,
  },
};
 
// Optional chaining (?.) with nullish coalescing (??)
const dbHost = config?.database?.host ?? "127.0.0.1";
const dbPort = config?.database?.port ?? 3306;
const cacheHost = config?.cache?.host ?? "localhost"; // "localhost" (cache doesn't exist)
 
console.log(dbHost);   // "localhost"
console.log(dbPort);   // 5432
console.log(cacheHost); // "localhost"

Conditional Display Logic

javascriptjavascript
function getStatusBadge(user) {
  const isActive = user.lastLogin && Date.now() - new Date(user.lastLogin) < 86400000;
  const isPremium = user.subscription === "premium" || user.subscription === "enterprise";
  const isVerified = !!user.emailVerifiedAt;
 
  return {
    text: isActive ? "Online" : "Offline",
    color: isPremium ? "gold" : "gray",
    icon: isVerified ? "check" : "alert",
  };
}

Best Practices

Operator Guidelines

These practices make operator-heavy code readable and maintainable.

Use === and !== exclusively. Never use loose equality (== and !=) in production code. The type coercion rules are too complex and produce surprising results. Configure ESLint's eqeqeq rule to enforce this.

Use ?? instead of || for defaults when 0 or "" are valid. The || operator treats 0, "", and false as falsy, falling through to the default. The ?? operator only triggers on null and undefined, preserving valid falsy values.

Add parentheses for complex expressions. Even if you know the precedence rules, parentheses make the code's intent visible to every reader. (a && b) || c is clearer than a && b || c.

Prefer += over ++. Writing count += 1 is more readable than count++ and avoids the prefix/postfix confusion. The exception is simple for loop increments where i++ is universally understood.

Use optional chaining (?.) for nested property access. Instead of user && user.profile && user.profile.avatar, write user?.profile?.avatar. It is shorter and handles every null/undefined case in the chain.

Common Mistakes and How to Avoid Them

Operator Pitfalls

These operator-related mistakes cause bugs that are difficult to trace.

Using = instead of === in conditions. Writing if (x = 5) assigns 5 to x and always evaluates as truthy. Always use === for comparison. ESLint's no-cond-assign rule catches this.

Relying on || for defaults with valid falsy values. const count = userCount || 10 replaces 0 with 10, which is wrong if 0 is a valid count. Use ?? instead: const count = userCount ?? 10.

Forgetting operator precedence with &&, ||. true || false && false evaluates to true because && has higher precedence than ||. Use parentheses to make the intent explicit.

String comparison instead of numeric comparison. When both operands are strings, < and > compare Unicode values, not numbers. "10" < "9" is true because "1" comes before "9" in Unicode. Convert to numbers first for numeric comparison.

Using + with strings and numbers accidentally. "5" + 3 returns "53" (string concatenation), not 8. Always convert string inputs to numbers with Number() or parseInt() before arithmetic.

Next Steps

Master operator precedence

Practice complex expressions and learn to read evaluation order without relying on parentheses as a crutch.

Learn conditional statements

Apply comparison and logical operators in if/else and switch statements for real control flow patterns.

Explore loops with arithmetic

Use arithmetic and comparison operators in for, while, and do-while loops to process data collections.

Build a calculator

Create a JavaScript calculator that handles operator precedence, parentheses, and edge cases like division by zero.

Rune AI

Rune AI

Key Insights

  • Always use === over ==: strict equality prevents type coercion surprises
  • Use ?? for defaults with valid falsy values: the nullish coalescing operator only catches null and undefined
  • Short-circuit evaluation is a feature: && and || can replace simple if statements for concise conditional logic
  • Parentheses clarify precedence: use them in complex expressions even when not strictly required
  • Optional chaining (?.) simplifies nested access: it replaces verbose null-checking chains with clean, readable syntax
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between == and === in JavaScript?

The `==` operator (loose equality) converts both operands to the same type before comparing, which means `5 == "5"` is `true`. The `===` operator (strict equality) compares both value AND type without any conversion, so `5 === "5"` is `false`. Always use `===` in production code to avoid unexpected type coercion.

What is short-circuit evaluation in JavaScript?

[Short-circuit](/tutorials/programming-languages/javascript/javascript-logical-short-circuiting-complete-guide) evaluation means logical operators stop evaluating as soon as the result is determined. With `&&`, if the left side is falsy, the right side is never evaluated (the result is already false). With `||`, if the left side is truthy, the right side is skipped. This is used for conditional execution: `user && user.save()` only calls `save()` if `user` is truthy.

What is the difference between || and ?? in JavaScript?

The `||` (logical OR) operator returns the first truthy value, treating `0`, `""`, and `false` as falsy. The `??` (nullish coalescing) operator only treats `null` and `undefined` as "missing," preserving other falsy values. Use `??` when `0` or `""` are valid values: `const count = input ?? 10` keeps `0` as-is, while `const count = input || 10` replaces `0` with `10`.

How does operator precedence work in JavaScript?

Operator precedence determines which operators execute first in a complex expression. Higher-precedence operators run before lower-precedence ones. For example, `*` has higher precedence than `+`, so `2 + 3 * 4` equals `14`, not `20`. Parentheses override precedence: `(2 + 3) * 4` equals `20`. When in doubt, use parentheses to make evaluation order explicit.

What does the typeof operator return for different values?

The `typeof` operator returns a string indicating the type: `"number"` for numbers, `"string"` for strings, `"boolean"` for booleans, `"undefined"` for undefined, `"object"` for objects (including null and arrays), `"function"` for functions, `"symbol"` for symbols, and `"bigint"` for BigInt values. The `typeof null === "object"` result is a well-known bug from JavaScript's original implementation.

Conclusion

JavaScript operators form the foundation of every expression, from simple arithmetic to complex conditional logic. The three most impactful habits are: using strict equality (===) consistently, choosing ?? over || for default values when falsy values are valid, and adding parentheses to complex expressions for clarity. Combined with optional chaining (?.) for safe property access and logical assignment operators (??=, ||=, &&=) for concise updates, these operators give you the tools to write expressive, bug-free JavaScript.