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.
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:
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)| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 10 + 3 | 13 |
- | Subtraction | 10 - 3 | 7 |
* | Multiplication | 10 * 3 | 30 |
/ | Division | 10 / 3 | 3.333... |
% | Modulo (remainder) | 10 % 3 | 1 |
** | Exponentiation | 10 ** 3 | 1000 |
+ (unary) | Numeric conversion | +"42" | 42 |
- (unary) | Negation | -42 | -42 |
Practical Arithmetic Patterns
// 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); // 100Increment and Decrement
The ++ and -- operators add or subtract 1. Their position (prefix vs postfix) determines when the change takes effect:
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); // 7Avoid 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).
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:
// 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" | false | true | Loose coerces string to number |
0 vs false | false | true | Loose coerces boolean to number |
"" vs false | false | true | Loose coerces both to 0 |
null vs undefined | false | true | Loose treats them as equal |
NaN vs NaN | false | false | NaN is never equal to anything |
[] vs false | false | true | Loose 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:
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()); // trueLogical 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:
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:
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):
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:
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(!!{}); // trueAssignment Operators
Assignment operators combine an operation with assignment in a single step:
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"| Operator | Equivalent | Description |
|---|---|---|
+= | x = x + right | Add and assign |
-= | x = x - right | Subtract and assign |
*= | x = x * right | Multiply and assign |
/= | x = x / right | Divide and assign |
%= | x = x % right | Modulo and assign |
**= | x = x ** right | Exponent and assign |
??= | x = x ?? right | Nullish coalescing assign |
||= | x = x || right | Logical OR assign |
&&= | x = x && right | Logical AND assign |
Operator Precedence
When multiple operators appear in one expression, precedence rules determine the order of evaluation. Higher precedence operators execute first.
// 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)| Precedence | Operator(s) | Category |
|---|---|---|
| Highest | () | Grouping |
| ↑ | ++, --, !, typeof, + (unary), - (unary) | Unary |
| ↑ | ** | Exponentiation |
| ↑ | *, /, % | Multiplicative |
| ↑ | +, - | Additive |
| ↑ | <, >, <=, >=, instanceof, in | Relational |
| ↑ | ===, !==, ==, != | 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
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
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
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
Key Insights
- Always use === over ==: strict equality prevents type coercion surprises
- Use ?? for defaults with valid falsy values: the nullish coalescing operator only catches
nullandundefined - 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
Frequently Asked Questions
What is the difference between == and === in JavaScript?
What is short-circuit evaluation in JavaScript?
What is the difference between || and ?? in JavaScript?
How does operator precedence work in JavaScript?
What does the typeof operator return for different values?
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.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.