Skip to content
JavaScript js fundamentals 4 min read

Comparison Operators

Comparison operators let you ask questions about values: are these two things equal, is this number larger than that one, does this object match another? In JavaScript these questions are deceptively subtle because the language offers two flavors of equality — loose (==) and strict (===) — that behave very differently. Understanding which to reach for is one of the highest-leverage habits you can build, because the wrong choice quietly introduces bugs through invisible type coercion.

Equality operators

JavaScript has two equality operators and their negated counterparts. The strict operators compare both value and type, while the loose operators first coerce operands to a common type before comparing.

OperatorNameCoerces types?1 == "1"1 === "1"
===Strict equalityNofalse
!==Strict inequalityNotrue
==Loose equalityYestrue
!=Loose inequalityYesfalse
console.log(1 === 1);      // same type, same value
console.log(1 === "1");    // number vs string
console.log(0 === false);  // number vs boolean
console.log("a" !== "b");

Output:

true
false
false
true

Strict equality is predictable: if the types differ, the result is always false. No surprises, no hidden conversions.

Loose equality and coercion

Loose equality (==) runs an “abstract equality” algorithm that converts operands until their types match. This produces results that are technically defined but rarely what you intend. The table below shows comparisons that are all true under ==:

ExpressionResult with ==Why
0 == ""trueboth coerce to 0
0 == falsetruefalse becomes 0
"" == falsetrueboth coerce to 0
1 == truetruetrue becomes 1
null == undefinedtruespecial-cased in the spec
" \t\n" == 0truewhitespace string coerces to 0
console.log(0 == "");          // empty string -> 0
console.log("0" == false);     // both -> 0
console.log(null == undefined);
console.log(null == 0);        // null only loosely equals undefined
console.log(NaN == NaN);       // NaN is never equal to anything

Output:

true
true
true
false
false

Notice null == 0 is false even though null == undefined is truenull and undefined are loosely equal only to each other, never to 0, "", or false. And NaN famously equals nothing, including itself.

The one defensible use of == is x == null, which is a concise idiom that checks for both null and undefined at once. Outside of that, prefer ===.

Relational operators

The relational operators <, >, <=, and >= compare ordering. For two numbers they compare numerically; if both operands are strings they compare lexicographically by Unicode code point; otherwise operands are coerced to numbers.

console.log(10 > 9);
console.log("apple" < "banana");   // string comparison
console.log("Z" < "a");            // uppercase sorts before lowercase
console.log("10" < "9");           // string compare: "1" < "9"
console.log("10" < 9);             // numeric coercion: 10 < 9

Output:

true
true
true
true
false

The "10" < "9" gotcha is a classic: as strings they compare character by character, so "1" (code point 49) is less than "9" (code point 57). Convert to numbers explicitly when you mean numeric ordering.

NaN poisons every relational comparison: NaN < 1, NaN > 1, and NaN === NaN are all false. Guard with Number.isNaN() before comparing computed numbers.

Comparing objects by reference

Objects, arrays, and functions are compared by reference identity, not by content. Two distinct objects with identical properties are never equal — only two references to the same object are.

const a = { x: 1 };
const b = { x: 1 };
const c = a;

console.log(a === b);   // different objects in memory
console.log(a === c);   // same reference
console.log([1, 2] === [1, 2]);

Output:

false
true
false

To compare objects by value you must do it yourself — shallow comparison of keys, a deep-equality helper, or comparing serialized forms like JSON.stringify(a) === JSON.stringify(b) (with the caveat that stringify ignores key order and chokes on functions and cyclic structures).

Object.is

Object.is(a, b) behaves almost identically to ===, but fixes the two long-standing quirks of strict equality: it treats NaN as equal to itself, and it distinguishes +0 from -0.

console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log(0 === -0);
console.log(Object.is(0, -0));
console.log(Object.is(1, 1));

Output:

false
true
true
false
true

Reach for Object.is when you specifically need correct NaN handling or signed-zero distinction; for everyday equality, === is the right default.

Best Practices

  • Default to === and !== everywhere — they are predictable and never coerce types.
  • Avoid == and !=; the single exception worth allowing is the x == null null-or-undefined check.
  • Convert types explicitly with Number(value) or String(value) before comparing rather than relying on implicit coercion.
  • Remember objects and arrays compare by reference; write a value-equality helper when you need to compare contents.
  • Use Number.isNaN() to test for NaN — never value === NaN, which is always false.
  • Use Object.is() only when you need exact NaN or ±0 semantics; otherwise === is clearer.
  • Enable a linter rule (such as ESLint’s eqeqeq) to flag accidental loose equality in code review.
Last updated June 1, 2026
Was this helpful?