Skip to main content
JavaScript beginner Lesson 5 of 24

JavaScript Operators

Master JavaScript operators — arithmetic, comparison, logical, nullish coalescing, optional chaining, spread, and ternary — with real-world examples.

Arithmetic Operators

Arithmetic operators perform math on numeric values and are the foundation for any calculation in your program. Standard math operations work as expected, with a few JavaScript-specific behaviors worth knowing — particularly around +, which doubles as string concatenation, and the way operator precedence interacts with mixed types.

console.log(10 + 3);   // 13
console.log(10 - 3);   // 7
console.log(10 * 3);   // 30
console.log(10 / 3);   // 3.3333...
console.log(10 % 3);   // 1   (remainder / modulo)
console.log(10 ** 3);  // 1000 (exponentiation, ES2016)

// Increment / decrement
let x = 5;
x++;   // post-increment: use x, then add 1
++x;   // pre-increment: add 1, then use x
x--;   // post-decrement
--x;   // pre-decrement

// + with strings: concatenation, not addition
"5" + 3      // "53"   ← 3 is coerced to "3"
5 + 3 + "px" // "8px"  ← left to right: 5+3=8, then "8"+"px"
"px" + 5 + 3 // "px53" ← "px"+"5"="px5", then "px5"+"3"

Assignment Operators

Assignment operators combine a math operation with assignment in a single step, reducing repetition. They’re the idiomatic way to update a variable based on its current value. ES2021 also added logical assignment operators that conditionally assign only when certain conditions are met.

let n = 10;
n += 5;   // n = n + 5  → 15
n -= 3;   // n = n - 3  → 12
n *= 2;   // n = n * 2  → 24
n /= 4;   // n = n / 4  → 6
n **= 2;  // n = n ** 2 → 36
n %= 10;  // n = n % 10 → 6

// Logical assignment operators (ES2021)
let a = null;
a ??= "default";   // assigns only if a is null/undefined → "default"

let b = 0;
b ||= 42;          // assigns if b is falsy → 42

let c = 5;
c &&= c * 2;       // assigns if c is truthy → 10

Comparison Operators

Strict equality (===) vs. loose equality (==)

Comparison operators evaluate a relationship between two values and return a boolean. The single most important comparison distinction in JavaScript is between strict equality (===) and loose equality (==). Strict equality checks both value and type with no conversion — it behaves predictably. Loose equality coerces types before comparing, which produces results that surprise almost every developer at some point.

// === checks value AND type — no coercion
1 === 1           // true
1 === "1"         // false  (number vs string)
null === null     // true
null === undefined // false

// == coerces types before comparing — full of surprises
1 == "1"          // true   ← string coerced to number
0 == false        // true   ← false coerced to 0
0 == ""           // true   ← "" coerced to 0
"" == false       // true
null == undefined // true   ← the one useful == behavior
null == 0         // false  ← null only == null or undefined

Rule of thumb: always use ===. The only exception is value == null to check for both null and undefined at once.

Relational operators

5 > 3      // true
5 < 3      // false
5 >= 5     // true
5 <= 4     // false

// String comparison is lexicographic (alphabetical by Unicode code point)
"banana" > "apple"   // true
"10" > "9"           // false  ← "1" comes before "9" in Unicode
10 > 9               // true   ← use numbers for numeric comparison

Logical Operators

AND (&&), OR (||), NOT (!)

Logical operators let you combine boolean expressions and are the backbone of conditional logic. Beyond their basic boolean use, && and || have a powerful behavior called short-circuit evaluation: they stop evaluating as soon as the result is determined, and they return the actual value that decided the outcome — not just true or false. This makes them useful for much more than simple boolean checks.

true && true   // true
true && false  // false
false || true  // true
false || false // false
!true          // false
!false         // true

Short-circuit evaluation

// && returns the first falsy value, or the last value if all truthy
false && expensiveFunction()  // expensiveFunction never called — short-circuits
"hello" && 42                 // 42  (first is truthy, continues to second)
null && "anything"            // null (first is falsy, stops here)

// || returns the first truthy value, or the last value if all falsy
true || expensiveFunction()   // expensiveFunction never called — short-circuits
0 || "fallback"               // "fallback"  (0 is falsy, continues)
"exists" || "fallback"        // "exists"    (truthy, stops here)

// Practical: default value pattern (pre-ES2020 style)
const name = userInput || "Anonymous";

// Practical: guard against null before calling a method
user && user.save();

NOT (!) and double NOT (!!)

!0           // true
!"hello"     // false
!!0          // false  — double NOT converts any value to its boolean equivalent
!!"hello"    // true
!!null       // false
!![]         // true   — empty array is truthy

Nullish Coalescing (??)

Introduced in ES2020, ?? solves a real problem with ||: the fact that || falls back on any falsy value, including 0, "", and false — values that are perfectly valid in many situations. ?? is stricter: it only falls back when the left side is null or undefined. This makes it the right choice whenever you want to keep legitimate falsy values intact.

const userScore = 0;

// Wrong — || treats 0 as falsy, loses the real value
const display1 = userScore || "No score";  // "No score" ← bug!

// Correct — ?? only triggers on null/undefined
const display2 = userScore ?? "No score";  // 0 ← correct

// Real-world examples
const port  = process.env.PORT ?? 3000;         // keep 0 if PORT is "0"
const theme = user.preferences?.theme ?? "dark"; // default only if truly absent
const count = response.count ?? 0;              // treat missing as zero, not falsy

Optional Chaining (?.)

When you access properties on deeply nested objects, any level that is null or undefined will throw a TypeError and crash your program. Optional chaining short-circuits the entire expression to undefined instead, letting you safely traverse structures where any part might be absent — common when working with API responses, user configuration, or optional features.

const user = {
  name: "Alice",
  address: {
    city: "Tokyo"
  }
};

// Without optional chaining — verbose and fragile
const city = user && user.address && user.address.city;

// With optional chaining — clean and safe
const city2 = user?.address?.city;          // "Tokyo"
const zip   = user?.address?.zip;           // undefined (no error)
const first = user?.contacts?.[0]?.email;   // undefined (no error)

// Works with method calls too
const upper  = user?.name?.toUpperCase();   // "ALICE"
const result = obj?.method?.();             // calls method only if it exists

Combine with ?? for a safe default:

const city = user?.address?.city ?? "Unknown city";

Spread Operator (…)

The spread operator expands an iterable — an array, string, or object — into its individual elements in place. It’s one of the most versatile operators in modern JavaScript, used for copying arrays, merging objects, and passing variable-length argument lists to functions. Wherever you previously used concat, Object.assign, or apply, spread is usually cleaner.

// Merging arrays
const a = [1, 2, 3];
const b = [4, 5, 6];
const merged = [...a, ...b];       // [1, 2, 3, 4, 5, 6]
const copy   = [...a];             // shallow copy — new array, same elements

// Merging objects (later keys win)
const defaults = { theme: "dark", lang: "en", timeout: 5000 };
const userPrefs = { theme: "light" };
const config = { ...defaults, ...userPrefs };
// { theme: "light", lang: "en", timeout: 5000 }

// Spreading into function arguments
const numbers = [5, 1, 8, 3];
Math.max(...numbers);   // 8  — equivalent to Math.max(5, 1, 8, 3)

// Rest parameters — the reverse: collect remaining arguments into an array
function sum(first, ...rest) {
  return rest.reduce((acc, n) => acc + n, first);
}
sum(1, 2, 3, 4); // 10

Ternary Operator

The ternary operator is a compact inline conditional: condition ? valueIfTrue : valueIfFalse. It’s ideal for simple two-branch choices that would be verbose as a full if/else — particularly inside template literals or JSX where a statement can’t appear. The key is to keep it simple: nesting ternaries quickly becomes unreadable, and a regular if/else or lookup object is almost always clearer for complex logic.

const age = 20;
const label = age >= 18 ? "Adult" : "Minor";  // "Adult"

// Practical: conditional class name
const className = isActive ? "btn btn-active" : "btn";

// Practical: pluralization in template literals
const msg = `You have ${count} ${count === 1 ? "message" : "messages"}`;

// Avoid nesting ternaries — it hurts readability
// Bad:
const grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";

// Better: use if/else or a lookup object for multiple branches
const getGrade = (score) => {
  if (score >= 90) return "A";
  if (score >= 80) return "B";
  if (score >= 70) return "C";
  return "F";
};

Operator Precedence Quick Reference

When operators are combined in a single expression, JavaScript evaluates them in a fixed order. When in doubt, use parentheses to make the intent explicit — they cost nothing and prevent subtle bugs.

// Precedence: ** > * / % > + - > comparisons > && > || > ??
2 + 3 * 4      // 14  (not 20 — multiplication before addition)
(2 + 3) * 4    // 20  — parentheses override precedence

true || false && false   // true  (&& has higher precedence than ||)
(true || false) && false // false — parentheses change the grouping

// ?? and || cannot be mixed without parentheses — this is a SyntaxError
// null ?? "a" || "b"
(null ?? "a") || "b" // "a"
null ?? ("a" || "b") // "a"

What’s Next

With operators covered, the next tutorial explores control flow — if/else, switch, loops, and how to direct your program’s execution based on conditions.

Frequently Asked Questions

When should I use == instead of ===?
Almost never. The only widely accepted use of == is the null check: `value == null` catches both null and undefined in one expression. For everything else, use === to avoid coercion surprises.
What is the difference between ?? and ||?
|| treats any falsy value (0, '', false, NaN) as missing and falls back. ?? only treats null and undefined as missing. Use ?? when 0 or an empty string is a valid value you want to keep.
What does the optional chaining operator ?. do?
It short-circuits and returns undefined if the value to the left is null or undefined, instead of throwing a TypeError. It's essential for safely traversing nested objects where any level may be absent.