Comparison Operators
Comparison operators answer yes/no questions about two values. They power every if, every filter, every "is this the same?" check. JavaScript has two equality operators (== and ===), four ordering operators (<, >, <=, >=), and a handful of edge cases — NaN, -0, mixed types — that are worth knowing about.
Strict equality (===, !==)
=== is true if both operands have the same type and the same value. No coercion. This is the equality you want 95% of the time.
3 === 3; // true "hi" === "hi"; // true true === true; // true null === null; // true undefined === undefined; // true 3 === "3"; // false — different types 0 === false; // false null === undefined; // false 3 !== "3"; // true
Loose equality (==, !=)
== performs type coercion before comparing. The rules are stable but unintuitive in places.
3 == "3"; // true — string converted to number 0 == false; // true — boolean converted to number "" == 0; // true — empty string converted to number null == undefined; // true — special case [] == false; // true — array → "" → 0 → false [1] == 1; // true — array → "1" → 1 "1\n" == 1; // true — whitespace ignored when parsing as number NaN == NaN; // false (!) — NaN is never equal to anything, including itself null == 0; // false — null only loosely equals undefined
function greet(name) {
if (name == null) { // catches both null and undefined
name = "stranger";
}
console.log("Hello, " + name);
}Equality algorithms in the spec
Three algorithms live in the spec, all of which you can think of in plain English:
Strict Equality (
===) — same type, same value. NaN is never equal to anything.+0 === -0istrue.Loose Equality (
==) — if types differ, coerce by these rules: numbers and strings → both numbers; booleans → numbers; objects compared to primitives → unbox the object withvalueOf/toString. Then apply strict equality.SameValue (
Object.is) — like===, butNaNequalsNaNand+0does not equal-0. Used internally byMap,Setand React.
Ordering operators
5 > 3; // true
5 >= 5; // true
3 < 5; // true
3 <= 2; // false
// Strings compare lexicographically by code unit (roughly: dictionary order)
"apple" < "banana"; // true
"Z" < "a"; // true — uppercase letters have lower code points
"10" < "9"; // true — string comparison, "1" < "9"
"10" < 9; // false — number comparison, 10 < 9 is false
// Mixed types are coerced to numbers
"5" > 3; // true
"abc" > 1; // false — Number("abc") is NaN, all comparisons with NaN are falseComparing strings to numbers coerces the string to a number. Comparing two strings does not coerce — it walks character by character.
NaN comparisons
NaN is the only value that is not equal to itself. Every comparison involving NaN is false.
NaN === NaN; // false
NaN == NaN; // false
NaN > 0; // false
NaN < 0; // false
NaN >= NaN; // false
NaN <= NaN; // false
Number.isNaN(NaN); // true — the safe test
Number.isNaN("oops"); // false — only matches the actual NaN valueAlways test for NaN with Number.isNaN rather than x === NaN (which is always false).
Object.is
Object.is(a, b) is almost identical to ===, with two corrections:
Object.is(NaN, NaN); // true — === would be false
Object.is(+0, -0); // false — === would be true
Object.is(2, 2); // true
Object.is("a", "a"); // truetrue false true true
This matches the SameValue algorithm and is what Map keys, Set membership and React state equality use under the hood.
Objects and reference equality
Two objects compare equal only if they are the same reference. There is no built-in "deep equality" operator in JavaScript.
const a = { x: 1 };
const b = { x: 1 };
const c = a;
a === b; // false — different objects, same contents
a === c; // true — same reference
// Compare contents — quick-and-dirty
JSON.stringify(a) === JSON.stringify(b); // true, but slow and limited
// For real apps, use a deep-equal helper from a library (lodash isEqual, dequal)Chained comparisons don't work like math
let x = 5;
// LOOKS like "is x between 1 and 10?"
1 < x < 10; // true — but for the wrong reason!
// Parses as ((1 < x) < 10) → (true < 10) → (1 < 10) → true
// Try this:
1 < x < -1; // ALSO true! (true < -1 → 1 < -1 → false)
// wait — that's false, let me redo it
// Either way, the result has nothing to do with "between"Practical recipes
Common patterns
// Check for null or undefined
if (value == null) { /* both null and undefined */ }
// Range check
if (age >= 18 && age < 65) { /* working age */ }
// Empty string / empty array
if (str === "") { /* explicit empty string */ }
if (arr.length === 0) { /* empty array */ }
// Compare two arrays element-by-element
function arraysEqual(a, b) {
return a.length === b.length && a.every((v, i) => v === b[i]);
}
// Find a duplicate
new Set(arr).size !== arr.length; // true if there are duplicates