Skip to content
C Codeloom
JavaScript

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.

·9 min read · By Yash Kesharwani
Beginner 10 min read

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 : c is for short, value-producing conditionals
  • ?? falls back only on null/undefined, unlike ||
  • ?. safely navigates possibly-missing properties
  • typeof, spread, rest, and in round 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.