Docs

3.2-Comparison-Operators

3.2 Comparison Operators

Table of Contents

  1. Introduction
  2. Equality Operators
  3. Loose Equality (==)
  4. Strict Equality (===)
  5. Inequality Operators
  6. Relational Operators
  7. String Comparison
  8. Comparing Different Types
  9. Object Comparison
  10. Object.is() Method
  11. Special Value Comparisons
  12. Comparison Gotchas
  13. Best Practices

Introduction

Comparison operators compare two values and return a boolean (true or false). They are fundamental for conditional logic, sorting, and data validation.

Quick Reference

OperatorNameExampleResult
==Loose equality5 == "5"true
===Strict equality5 === "5"false
!=Loose inequality5 != "5"false
!==Strict inequality5 !== "5"true
>Greater than5 > 3true
<Less than5 < 3false
>=Greater than or equal5 >= 5true
<=Less than or equal5 <= 5true

Equality Operators

Two Types of Equality

   EQUALITY COMPARISON

   Loose Equality (==)         Strict Equality (===)
   ┌─────────────────┐         ┌──────────────────┐
   │  Compares VALUE │         │ Compares VALUE   │
   │  with type      │         │ AND TYPE         │
   │  coercion       │         │ No coercion      │
   └─────────────────┘         └──────────────────┘

   5 == "5"  → true            5 === "5"  → false
   (string converts to number) (different types)

The Same vs The Same Same

// Loose equality - values are "equivalent"
console.log(5 == '5'); // true

// Strict equality - values are "identical"
console.log(5 === '5'); // false

// Same type, same value
console.log(5 === 5); // true
console.log('5' === '5'); // true

Loose Equality (==)

Type Coercion Rules

When types differ, JavaScript converts values before comparing:

   TYPE COERCION ORDER (== operator)

   1. If same type → compare values
   2. null == undefined → true
   3. Number vs String → convert String to Number
   4. Boolean vs anything → convert Boolean to Number first
   5. Object vs Primitive → convert Object to primitive (valueOf/toString)

Number and String

console.log(5 == '5'); // true  ("5" → 5)
console.log(0 == ''); // true  ("" → 0)
console.log(0 == '0'); // true  ("0" → 0)
console.log(10 == '10.0'); // true  ("10.0" → 10)
console.log(1 == '1.0'); // true
console.log(42 == '42abc'); // false ("42abc" → NaN)

Boolean Conversions

// Boolean converts to number FIRST, then compares
console.log(true == 1); // true  (true → 1)
console.log(false == 0); // true  (false → 0)
console.log(true == '1'); // true  (true → 1, "1" → 1)
console.log(false == ''); // true  (false → 0, "" → 0)
console.log(true == 'true'); // false (true → 1, "true" → NaN)
console.log(false == 'false'); // false (false → 0, "false" → NaN)

null and undefined

// Special rule: null == undefined is true
console.log(null == undefined); // true
console.log(undefined == null); // true

// But null and undefined don't equal anything else
console.log(null == 0); // false
console.log(null == ''); // false
console.log(null == false); // false
console.log(undefined == 0); // false
console.log(undefined == ''); // false
console.log(undefined == false); // false

Object Comparisons with ==

// Objects convert to primitives for comparison
console.log([1] == 1); // true  ([1].toString() = "1" → 1)
console.log([1, 2] == '1,2'); // true  ([1,2].toString() = "1,2")
console.log([] == 0); // true  ([].toString() = "" → 0)
console.log([] == false); // true  ([] → "" → 0, false → 0)
console.log([''] == ''); // true
console.log({} == '[object Object]'); // true

Strict Equality (===)

No Type Coercion

// Different types → always false
console.log(5 === '5'); // false
console.log(0 === false); // false
console.log(null === undefined); // false
console.log(true === 1); // false

// Same type → compare values
console.log(5 === 5); // true
console.log('hello' === 'hello'); // true
console.log(true === true); // true

Why Use Strict Equality?

// Loose equality surprises:
console.log(0 == ''); // true  (unexpected!)
console.log(0 == '0'); // true
console.log('' == '0'); // false (inconsistent!)

// More surprises:
console.log(false == '0'); // true
console.log(false == []); // true
console.log([] == '0'); // false

// Strict equality is predictable:
console.log(0 === ''); // false (clear)
console.log(0 === '0'); // false (clear)
console.log('' === '0'); // false (clear)

Inequality Operators

Loose Inequality (!=)

// Returns true if values are NOT loosely equal
console.log(5 != '5'); // false (5 == "5" is true)
console.log(5 != 6); // true
console.log(null != undefined); // false (null == undefined)
console.log(0 != false); // false (0 == false)

Strict Inequality (!==)

// Returns true if values are NOT strictly equal
console.log(5 !== '5'); // true (different types)
console.log(5 !== 5); // false
console.log(null !== undefined); // true (different types)
console.log(0 !== false); // true (different types)

Relational Operators

Greater Than and Less Than

console.log(5 > 3); // true
console.log(5 < 3); // false
console.log(5 > 5); // false
console.log(5 < 5); // false

Greater Than or Equal, Less Than or Equal

console.log(5 >= 5); // true
console.log(5 >= 3); // true
console.log(5 >= 7); // false

console.log(5 <= 5); // true
console.log(5 <= 7); // true
console.log(5 <= 3); // false

Type Coercion in Relational Operators

// String to Number
console.log('10' > 5); // true ("10" → 10)
console.log('10' > '5'); // false (string comparison!)

// Boolean to Number
console.log(true > 0); // true (1 > 0)
console.log(false >= 0); // true (0 >= 0)

// null and undefined
console.log(null >= 0); // true (null → 0)
console.log(null > 0); // false (0 > 0)
console.log(null == 0); // false (special rule!)

console.log(undefined >= 0); // false (undefined → NaN)
console.log(undefined < 0); // false (NaN comparisons)

String Comparison

Lexicographic (Dictionary) Order

Strings are compared character by character using Unicode values:

console.log('a' < 'b'); // true
console.log('apple' < 'banana'); // true
console.log('Apple' < 'apple'); // true (uppercase < lowercase)
console.log('10' < '9'); // true (string comparison!)
console.log('10' < '2'); // true ("1" < "2" in first char)

Unicode Code Points

// Get character code
console.log('A'.charCodeAt(0)); // 65
console.log('B'.charCodeAt(0)); // 66
console.log('a'.charCodeAt(0)); // 97
console.log('b'.charCodeAt(0)); // 98

// Comparison uses these codes
console.log('A' < 'B'); // true (65 < 66)
console.log('A' < 'a'); // true (65 < 97)
console.log('Z' < 'a'); // true (90 < 97)

Case-Insensitive Comparison

let str1 = 'Apple';
let str2 = 'apple';

// Case-sensitive (default)
console.log(str1 === str2); // false
console.log(str1 < str2); // true

// Case-insensitive
console.log(str1.toLowerCase() === str2.toLowerCase()); // true

// Using localeCompare
console.log(str1.localeCompare(str2, undefined, { sensitivity: 'base' })); // 0

Sorting Strings Correctly

let words = ['banana', 'Apple', 'cherry', 'apricot'];

// Default sort (case-sensitive)
console.log(words.sort());
// ["Apple", "apricot", "banana", "cherry"]

// Case-insensitive sort
console.log(
  words.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
);
// ["Apple", "apricot", "banana", "cherry"]

Comparing Different Types

Comparison Coercion Table

   TYPE COERCION FOR COMPARISONS

   Left        Right       Coercion Applied
   ────────────────────────────────────────
   Number      String      String → Number
   String      Number      String → Number
   Boolean     Any         Boolean → Number
   Object      Primitive   Object → Primitive
   null        undefined   Equal (special case)

Examples

// Number vs String
console.log(10 > '5'); // true (10 > 5)
console.log(10 > '50'); // false (10 > 50)
console.log('10' > 5); // true (10 > 5)

// Boolean vs Number
console.log(true > 0); // true (1 > 0)
console.log(false < 1); // true (0 < 1)

// Boolean vs String
console.log(true > '0'); // true (1 > 0)
console.log(false == '0'); // true (0 == 0)

// With arrays
console.log([10] > 5); // true ([10] → "10" → 10)
console.log([1, 2] > 5); // false ([1,2] → "1,2" → NaN)

Object Comparison

Reference Equality

Objects are compared by reference, not by value:

let obj1 = { a: 1 };
let obj2 = { a: 1 };
let obj3 = obj1;

console.log(obj1 === obj2); // false (different references)
console.log(obj1 === obj3); // true (same reference)
console.log(obj1 == obj2); // false (still different refs)

Array Comparison

let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
let arr3 = arr1;

console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // true
console.log(arr1 == arr2); // false

// Compare by value - need to convert
console.log(JSON.stringify(arr1) === JSON.stringify(arr2)); // true
console.log(arr1.toString() === arr2.toString()); // true

Deep Equality

// Simple deep equality function
function deepEqual(obj1, obj2) {
  if (obj1 === obj2) return true;

  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
    return false;
  }

  if (obj1 === null || obj2 === null) return false;

  let keys1 = Object.keys(obj1);
  let keys2 = Object.keys(obj2);

  if (keys1.length !== keys2.length) return false;

  for (let key of keys1) {
    if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
      return false;
    }
  }

  return true;
}

console.log(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })); // true
console.log(deepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })); // false

Object.is() Method

Difference from ===

Object.is() is similar to === but handles two edge cases differently:

// Case 1: NaN
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true

// Case 2: Positive and Negative Zero
console.log(0 === -0); // true
console.log(Object.is(0, -0)); // false
console.log(Object.is(-0, -0)); // true

// Otherwise same as ===
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, '5')); // false
console.log(Object.is({}, {})); // false

Comparison Summary

xy=====Object.is()
55truetruetrue
5"5"truefalsefalse
NaNNaNfalsefalsetrue
+0-0truetruefalse
nullundefinedtruefalsefalse

Special Value Comparisons

NaN Comparisons

// NaN is not equal to anything, including itself
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(NaN != NaN); // true

// Checking for NaN
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('NaN')); // false (correct behavior)
console.log(isNaN('NaN')); // true (coerces to NaN first)

null and undefined

// Only equal to each other (loose)
console.log(null == undefined); // true
console.log(null === undefined); // false

// null comparisons with relational operators
console.log(null >= 0); // true (null → 0)
console.log(null <= 0); // true (null → 0)
console.log(null == 0); // false (special rule!)
console.log(null > 0); // false
console.log(null < 0); // false

Infinity

console.log(Infinity > 100); // true
console.log(Infinity > Infinity); // false
console.log(Infinity === Infinity); // true
console.log(-Infinity < Infinity); // true
console.log(-Infinity === -Infinity); // true

Comparison Gotchas

The Transitivity Problem

// Loose equality is not transitive!
console.log('0' == 0); // true
console.log(0 == ''); // true
console.log('0' == ''); // false (should be true if transitive!)

Array Comparison Gotchas

// Empty array comparisons
console.log([] == false); // true
console.log([] == 0); // true
console.log([] == ''); // true
console.log([] == []); // false (different references!)

// Array with one element
console.log([0] == false); // true
console.log([1] == true); // true
console.log([2] == true); // false (2 !== 1)

String vs Number Comparison

// String comparison is lexicographic
console.log('100' > '99'); // false (1 < 9)
console.log('100' > '9'); // false (1 < 9)

// Number comparison
console.log(100 > 99); // true
console.log(100 > 9); // true

// Mixed - converts to number
console.log('100' > 99); // true
console.log('100' > 9); // true

The typeof null Bug

// This can cause comparison issues
let value = null;

// Wrong way to check
if (typeof value === 'object') {
  // This runs for null too!
  console.log("It's an object");
}

// Right way
if (value !== null && typeof value === 'object') {
  console.log("It's a real object");
}

Best Practices

1. Always Use Strict Equality

// ❌ Avoid
if (x == null) {
}
if (value == 0) {
}

// ✅ Prefer
if (x === null || x === undefined) {
}
if (value === 0) {
}

// Exception: null check for both null/undefined
if (x == null) {
} // OK if you mean null OR undefined

2. Be Explicit with Type Conversions

// ❌ Implicit conversion
if (userInput == 42) {
}

// ✅ Explicit conversion
if (Number(userInput) === 42) {
}
// or
if (parseInt(userInput, 10) === 42) {
}

3. Use Proper Type Checking

// ❌ Loose type check
if (value == null) {
}

// ✅ Explicit checks
if (value === null) {
}
if (value === undefined) {
}
if (value === null || value === undefined) {
}
if (value == null) {
} // OK when intentional

4. String Comparison Best Practices

// ❌ Case-sensitive by default
if (input === 'yes') {
}

// ✅ Handle case variations
if (input.toLowerCase() === 'yes') {
}

// ✅ For locale-aware comparison
if (str1.localeCompare(str2) === 0) {
}

5. Object Comparison

// ❌ Won't work
if (arr1 === arr2) {
} // Compares references

// ✅ Compare content
if (JSON.stringify(arr1) === JSON.stringify(arr2)) {
}

// ✅ Or use a proper comparison function
if (deepEqual(obj1, obj2)) {
}

6. NaN Checking

// ❌ Won't work
if (value === NaN) {
}

// ✅ Use Number.isNaN
if (Number.isNaN(value)) {
}

// ✅ Or for older browsers
if (value !== value) {
} // NaN is the only value not equal to itself

Summary

OperatorNameType CoercionUse Case
===Strict EqualNoDefault choice
!==Strict Not EqualNoDefault choice
==Loose EqualYesOnly for null check
!=Loose Not EqualYesRarely
>Greater ThanYesNumeric comparison
<Less ThanYesNumeric comparison
>=Greater/EqualYesNumeric comparison
<=Less/EqualYesNumeric comparison

Key Rules

  1. Use === and !== by default
  2. == is only useful for x == null (checks both null and undefined)
  3. Objects compare by reference, not value
  4. String comparison is lexicographic
  5. NaN !== NaN - use Number.isNaN()
  6. Object.is() handles NaN and ±0 differently

Next Steps

Continue learning operators:

.2 Comparison Operators - JavaScript Tutorial | DeepML