JavaScript Operators and Comparisons (==, ===, &&, ||)
A complete beginner's guide to JavaScript operators — arithmetic, comparison, logical, assignment, and the modern nullish coalescing and optional chaining operators.
What you'll learn
- ✓Every comparison operator and why === is preferred
- ✓Logical && and || with short-circuit evaluation
- ✓The ternary expression for compact conditionals
- ✓Modern operators: ?? (nullish), ?. (optional chaining), ??=
- ✓Operator precedence in plain terms
Prerequisites
- •JavaScript objects — see Objects
Operators are the glue between values. A handful of them — +, ===, &&, ||, ?:, ??, ?. — appear in nearly every line of real JavaScript. Learning them well makes everything that follows easier.
Arithmetic (a quick recap)
Covered in detail in Numbers and Math:
2 + 3 // 5
10 - 4 // 6
3 * 4 // 12
10 / 3 // 3.333...
10 % 3 // 1
2 ** 10 // 1024
Remember + also concatenates strings — and converts numbers to strings if either operand is a string. Prefer template literals for building strings.
Assignment
The basic assignment = and the compound forms:
let n = 10;
n += 5; // n = n + 5
n -= 3; // n = n - 3
n *= 2; // n = n * 2
n /= 4; // n = n / 4
n %= 3; // n = n % 3
n **= 2; // n = n ** 2
And three modern logical assignments we’ll meet again below:
let a;
a ??= "default"; // assign if a is null or undefined
let b = 0;
b ||= 100; // assign if b is falsy
let c = 5;
c &&= c * 2; // assign if c is truthy
Comparison: == vs. ===
JavaScript has two equality operators. The difference matters.
=== (strict equality) compares both type and value. No conversions:
5 === 5; // true
5 === "5"; // false — different types
true === 1; // false
null === undefined; // false
== (loose equality) converts types first, then compares. This produces some famously surprising results:
5 == "5"; // true — string coerced to number
true == 1; // true
null == undefined; // true (only)
0 == ""; // true
0 == false; // true
"" == false; // true
[] == false; // true
[] == ![]; // true — yes, really
Modern style is: always use ===. It does what you expect, and the few cases where loose equality is genuinely useful (checking for “null or undefined”) are better expressed with value == null only if you really want to, or value === null || value === undefined.
The same applies to the inequality operators — prefer !== over !=.
5 !== "5"; // true — different types
Relational operators
3 < 5; // true
5 > 3; // true
3 <= 3; // true
3 >= 4; // false
These convert their operands to numbers (or compare strings lexicographically if both are strings — see the strings post):
"apple" < "banana"; // true
"10" < "9"; // true — string comparison, '1' < '9'
"10" < 9; // false — coerced to numbers, 10 < 9
When in doubt, convert explicitly to avoid surprises.
Logical operators
The three logical operators — && (and), || (or), ! (not) — work on any values, not just booleans, thanks to short-circuit evaluation.
&& — and
a && b evaluates a. If a is falsy, it returns a. Otherwise it returns b:
true && "hello"; // 'hello'
false && "hello"; // false
0 && "hello"; // 0
"yes" && "hello"; // 'hello'
In if statements this just looks like “both must be true”:
if (user && user.isAdmin) {
// safe to access user.isAdmin
}
|| — or
a || b evaluates a. If a is truthy, it returns a. Otherwise it returns b:
true || "default"; // true
false || "default"; // 'default'
"" || "default"; // 'default'
"hello" || "default"; // 'hello'
A common idiom is to use || to supply a default:
function greet(name) {
name = name || "stranger";
console.log(`Hello, ${name}`);
}
But beware — || treats 0, "", and false as falsy, which may not be what you want. For “default only when the value is missing”, use ?? (covered next).
! — not
!true; // false
!0; // true — 0 is falsy
!""; // true — empty string is falsy
!!"hello"; // true — common idiom to coerce to boolean
The double-bang !!x is a concise way to convert any value to its boolean equivalent — equivalent to Boolean(x).
The ternary operator
condition ? a : b is a compact if/else that produces a value:
const age = 17;
const status = age >= 18 ? "adult" : "minor";
console.log(status); // 'minor'
Use it for short choices that fit naturally on one line. For anything longer or branched, an if statement is clearer.
// Good
const message = isLoggedIn ? "Welcome back" : "Please sign in";
// Avoid — too much logic for a ternary
const result = a > 0 ? (a > 100 ? "huge" : "positive") : a === 0 ? "zero" : "negative";
Nullish coalescing: ??
?? is like ||, but it only falls back when the left side is null or undefined — not for 0, "", or false.
0 || 100; // 100 — 0 is falsy
0 ?? 100; // 0 — 0 is a real value
"" || "default"; // 'default'
"" ?? "default"; // ''
null ?? "default"; // 'default'
undefined ?? "default"; // 'default'
This is exactly the operator you want for defaulting values where 0 and empty string are legitimate inputs:
function createUser({ name, age, points }) {
return {
name,
age: age ?? 0,
points: points ?? 0,
};
}
createUser({ name: "Ada", age: 0, points: 0 });
// { name: 'Ada', age: 0, points: 0 } — correct
If we had used ||, the zeros would have been replaced by the defaults. ?? was added in 2020 specifically to fix this whole category of bug.
Optional chaining: ?.
?. lets you access a property only if the thing on the left isn’t null or undefined. Otherwise the whole expression short-circuits to undefined:
const user = { name: "Ada", address: { city: "London" } };
console.log(user.address.city); // 'London'
console.log(user.contact.email); // TypeError — contact is undefined
console.log(user.contact?.email); // undefined — no error
console.log(user.address?.city); // 'London'
It also works on function calls and array indices:
const callback = undefined;
callback?.(); // undefined — no error
const arr = null;
arr?.[0]; // undefined
Combine with ?? for a clean “get this nested value or use a default”:
const city = user.address?.city ?? "Unknown";
These two operators (?. and ??) replaced a generation of defensive code:
// Old way
const city = user && user.address && user.address.city ? user.address.city : "Unknown";
// Modern way
const city = user?.address?.city ?? "Unknown";
Try it yourself. Given this data, write expressions to safely get each value, defaulting to "N/A" if any link in the chain is missing:
const data = {
user: { name: "Ada" },
// user.profile is missing
};
const name = /* ? */;
const bio = /* ? */;
const followers = /* ? */;Use ?. and ?? together. The answers are 'Ada', 'N/A', and 'N/A'.
The spread, rest, and typeof operators
Quick recap of operators we’ve met in earlier posts.
typeof
Returns a string describing a type:
typeof 42; // 'number'
typeof "hi"; // 'string'
typeof undefined; // 'undefined'
typeof null; // 'object' — historical quirk
Spread (... in expressions)
Expands an iterable into elements:
const a = [1, 2, 3];
const b = [0, ...a]; // [0, 1, 2, 3]
Math.max(...a); // 3
Rest (... in parameters)
Collects remaining arguments into an array:
function sum(...nums) {
return nums.reduce((s, n) => s + n, 0);
}
sum(1, 2, 3, 4); // 10
The in operator
Checks whether a property exists on an object (or its prototype):
const user = { name: "Ada", age: 36 };
"name" in user; // true
"email" in user; // false
For arrays, this checks indices, not values — use includes instead:
const a = ["red", "blue"];
0 in a; // true — there's an index 0
"red" in a; // false — wrong tool
a.includes("red"); // true
Operator precedence
JavaScript follows mathematical precedence: ** before * and /, * and / before + and -, arithmetic before comparison, comparison before logical:
2 + 3 * 4 === 14; // true — *, then +, then ===
!true || false; // false — !, then ||
true || false && false; // true — && before ||
The exhaustive table is long and rarely worth memorising. The professional habit: when in doubt, add parentheses. They cost nothing and make intent obvious.
// Clearer
((isAdmin && hasAccess) || isOwner) && canEdit;
A small worked example
A function that returns a display-ready user record, gracefully handling missing data.
function formatUser(user) {
const name = user?.name ?? "Unknown";
const role = user?.role ?? "guest";
const lastSeen = user?.activity?.lastSeen ?? "never";
const isActive =
user?.activity?.lastSeen && Date.now() - user.activity.lastSeen < 30_000;
return `${name} (${role}) — ${isActive ? "online" : `last seen ${lastSeen}`}`;
}
console.log(formatUser({
name: "Ada",
role: "admin",
activity: { lastSeen: Date.now() - 5000 },
}));
// 'Ada (admin) — online'
console.log(formatUser({ name: "Linus" }));
// 'Linus (guest) — last seen never'
console.log(formatUser(null));
// 'Unknown (guest) — last seen never'
?., ??, &&, the ternary — five lines of operators replace fifteen lines of defensive if checks.
Recap
You now know:
- Always use
===and!==, not==and!= &&and||use short-circuit evaluation and return values, not just booleans- The ternary
a ? b : cis for short, value-producing conditionals ??falls back only onnull/undefined, unlike||?.safely navigates possibly-missing propertiestypeof, spread, rest, andinround out the everyday set- Add parentheses for clarity rather than memorising precedence tables
Next steps
We have all the building blocks. Time to put them in motion — the conditionals and loops that turn data into behaviour.
→ Next: Conditionals and Loops in JavaScript
Questions or feedback? Email codeloomdevv@gmail.com.