JavaScript Operators: Complete Reference Guide

Operators are the fundamental building blocks that manipulate values in JavaScript. Unlike functions, operators use special syntax and are deeply integrated into the language's grammar. While `add(2,...

Key Insights

  • JavaScript has over 50 operators spanning arithmetic, logical, bitwise, and modern features like optional chaining—understanding their precedence and associativity prevents subtle bugs
  • Strict equality (===) should be your default choice over loose equality (==) to avoid unexpected type coercion, and the nullish coalescing operator (??) provides safer default values than logical OR (||)
  • Bitwise operators aren’t just academic—they’re practical for permission systems and flags, while modern operators like optional chaining (?.) and logical assignment (??=) dramatically improve code safety and readability

Understanding JavaScript Operators

Operators are the fundamental building blocks that manipulate values in JavaScript. Unlike functions, operators use special syntax and are deeply integrated into the language’s grammar. While add(2, 3) is a function call, 2 + 3 uses the addition operator—more concise and often more performant.

Every operator has two critical properties: precedence (which operations execute first) and associativity (left-to-right or right-to-left evaluation). The expression 2 + 3 * 4 equals 14, not 20, because multiplication has higher precedence than addition. Understanding these rules prevents bugs that can plague production code.

Arithmetic & Assignment Operators

The arithmetic operators handle mathematical operations. Beyond basic addition (+), subtraction (-), multiplication (*), and division (/), JavaScript provides the modulo operator (%) for remainders and the exponentiation operator (**) for powers.

// Basic arithmetic
const sum = 10 + 5;        // 15
const difference = 10 - 5;  // 5
const product = 10 * 5;     // 50
const quotient = 10 / 5;    // 2
const remainder = 10 % 3;   // 1
const power = 2 ** 3;       // 8

// Practical use: compound interest calculator
function calculateCompoundInterest(principal, rate, years) {
  return principal * (1 + rate) ** years;
}

console.log(calculateCompoundInterest(1000, 0.05, 10)); // 1628.89

Increment (++) and decrement (--) operators modify values by one. They come in prefix and postfix variants with different behavior:

let counter = 5;
console.log(counter++); // 5 (returns then increments)
console.log(counter);   // 6

let score = 5;
console.log(++score);   // 6 (increments then returns)
console.log(score);     // 6

Assignment operators combine assignment with arithmetic operations. Use them to write more concise code:

let total = 100;
total += 50;  // total = total + 50 (150)
total -= 20;  // total = total - 20 (130)
total *= 2;   // total = total * 2 (260)
total /= 4;   // total = total / 4 (65)
total %= 7;   // total = total % 7 (2)
total **= 3;  // total = total ** 3 (8)

Comparison & Logical Operators

Comparison operators return boolean values and are essential for conditional logic. JavaScript provides two equality operators: loose (==) and strict (===). Always prefer strict equality unless you have a specific reason for type coercion.

// Type coercion with == (avoid this)
console.log(5 == "5");    // true (string coerced to number)
console.log(0 == false);  // true (false coerced to 0)
console.log(null == undefined); // true

// Strict equality with === (use this)
console.log(5 === "5");   // false
console.log(0 === false); // false
console.log(null === undefined); // false

Relational operators compare values:

// Form validation example
function validateAge(age) {
  if (age < 0) {
    return "Age cannot be negative";
  }
  if (age < 18) {
    return "Must be 18 or older";
  }
  if (age > 120) {
    return "Please enter a valid age";
  }
  return "Valid age";
}

Logical operators combine boolean expressions. The AND (&&), OR (||), and NOT (!) operators are standard, but the nullish coalescing operator (??) is a modern addition that only checks for null or undefined:

// Logical OR returns first truthy value
const username = "" || "Guest";  // "Guest" (empty string is falsy)

// Nullish coalescing returns first non-nullish value
const port = 0 ?? 3000;  // 0 (zero is not null/undefined)
const host = null ?? "localhost";  // "localhost"

// Conditional rendering pattern
function renderUser(user) {
  return user && user.isActive && `Welcome, ${user.name}`;
}

Bitwise & Special Operators

Bitwise operators manipulate individual bits and are surprisingly practical for permission systems and flags:

// Permission system using bitwise operators
const READ = 1;    // 001
const WRITE = 2;   // 010
const EXECUTE = 4; // 100

// Grant multiple permissions with OR
let permissions = READ | WRITE; // 011 (3)

// Check permission with AND
const canWrite = (permissions & WRITE) !== 0; // true
const canExecute = (permissions & EXECUTE) !== 0; // false

// Add permission
permissions |= EXECUTE; // 111 (7)

// Remove permission
permissions &= ~WRITE; // 101 (5)

The typeof operator identifies value types, while instanceof checks constructor inheritance:

// Type checking utility
function getType(value) {
  if (value === null) return "null";
  if (Array.isArray(value)) return "array";
  return typeof value;
}

console.log(typeof 42);           // "number"
console.log(typeof "hello");      // "string"
console.log([] instanceof Array); // true

The ternary operator provides concise conditional expressions:

const age = 20;
const status = age >= 18 ? "adult" : "minor";

// Nested ternaries (use sparingly)
const ticket = age < 12 ? "child" : age < 65 ? "adult" : "senior";

Optional chaining (?.) safely accesses nested properties:

const user = {
  profile: {
    name: "Alice"
  }
};

// Without optional chaining
const city = user && user.profile && user.profile.address && user.profile.address.city;

// With optional chaining
const cityNew = user?.profile?.address?.city; // undefined (no error)

// Works with methods and arrays
const result = obj?.method?.();
const item = array?.[0];

String & Spread/Rest Operators

The + operator concatenates strings, but template literals are more readable for complex strings:

const name = "Alice";
const age = 30;

// String concatenation
const greeting1 = "Hello, " + name + "! You are " + age + " years old.";

// Template literal (preferred)
const greeting2 = `Hello, ${name}! You are ${age} years old.`;

The spread operator (...) expands iterables and is invaluable for array and object manipulation:

// Array cloning and merging
const original = [1, 2, 3];
const clone = [...original];
const merged = [...original, 4, 5, 6];

// Object cloning and merging
const user = { name: "Alice", age: 30 };
const userWithEmail = { ...user, email: "alice@example.com" };

// Function arguments
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// Spreading into function calls
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3

Operator Precedence & Common Pitfalls

Operator precedence determines evaluation order. Multiplication and division execute before addition and subtraction. Assignment has very low precedence:

// Precedence bug
let x = 5;
let y = x + 2 * 3; // 11, not 21 (multiplication first)

// Assignment in condition (common mistake)
let value = 10;
if (value = 5) { // Assigns 5, always truthy
  console.log("This always runs");
}

// Correct comparison
if (value === 5) {
  console.log("Value is 5");
}

// Fix complex expressions with parentheses
const result = (x + 2) * 3; // 21 (explicit grouping)

Best practices for readability:

// Bad: relies on precedence knowledge
const total = price * quantity + tax - discount;

// Good: explicit grouping
const subtotal = price * quantity;
const total = (subtotal + tax) - discount;

// Bad: confusing equality
if (user.role == "admin") { }

// Good: strict equality
if (user.role === "admin") { }

Modern Operator Features (ES2020+)

Nullish coalescing (??) provides default values only for null or undefined, unlike || which triggers on any falsy value:

// Configuration with defaults
const config = {
  port: 0,
  debug: false,
  timeout: null
};

// Using || (problematic)
const port1 = config.port || 3000;  // 3000 (0 is falsy)
const debug1 = config.debug || true; // true (false is falsy)

// Using ?? (correct)
const port2 = config.port ?? 3000;  // 0 (0 is not nullish)
const debug2 = config.debug ?? true; // false
const timeout = config.timeout ?? 5000; // 5000

Logical assignment operators combine logical operations with assignment:

let user = { name: "Alice" };

// Only assign if nullish
user.role ??= "guest"; // user.role = user.role ?? "guest"

// Only assign if falsy
let count = 0;
count ||= 1; // count = count || 1 (becomes 1)

// Only assign if truthy
let settings = { theme: "dark" };
settings.theme &&= settings.theme.toUpperCase(); // "DARK"

// Practical example: cache pattern
const cache = {};
function getData(key) {
  cache[key] ??= expensiveOperation(key);
  return cache[key];
}

These modern operators reduce boilerplate and make intent clearer. Optional chaining with nullish coalescing creates robust default value patterns:

// Safe nested access with defaults
function getUserCity(user) {
  return user?.profile?.address?.city ?? "Unknown";
}

// API response handling
function processResponse(response) {
  const data = response?.data ?? [];
  const error = response?.error?.message ?? "Unknown error";
  return { data, error };
}

Master these operators and you’ll write more concise, safer JavaScript. Prioritize strict equality, embrace modern operators like optional chaining and nullish coalescing, and use parentheses when precedence isn’t obvious. Your future self—and your code reviewers—will thank you.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.