Skip to main content
Why does 1 == "1" return true but 1 === "1" return false? Why does typeof null return "object"? And why is NaN the only value in JavaScript that isn’t equal to itself?
Understanding JavaScript’s equality operators is crucial because comparison bugs are among the most common in JavaScript code. According to the ECMAScript specification (ECMA-262), JavaScript defines three distinct equality algorithms: Abstract Equality, Strict Equality, and SameValue. This guide will teach you exactly how ==, ===, and Object.is() work, and when to use each one.
What you’ll learn in this guide:
  • The difference between == (loose) and === (strict) equality
  • How JavaScript converts values during loose equality comparisons
  • The typeof operator and its famous quirks (including the null bug)
  • When to use Object.is() for edge cases like NaN and -0
  • Common comparison mistakes and how to avoid them
  • A simple rule: when to use which operator
Prerequisites: This guide assumes you understand Primitive Types and Type Coercion. Equality operators rely heavily on how JavaScript converts types. If those concepts are new to you, read those guides first!

The Three Equality Operators: Overview

JavaScript provides three ways to compare values for equality. Here’s the quick summary:
The simple rule: Always use === for comparisons. The only exception: use == null to check if a value is empty (null or undefined). You’ll rarely need Object.is(). It’s for special cases we’ll cover later.

The Teacher Grading Papers: A Real-World Analogy

Imagine a teacher grading a math test. The question asks: “What is 2 + 2?” One student writes: 4
Another student writes: "4" (as text)
A third student writes: 4.0
How strict should the teacher be when grading?
JavaScript gives you both types of teachers:
  • Loose equality (==) — The relaxed teacher. Accepts 4 and "4" as the same answer because the meaning is similar. Converts values to match before comparing.
  • Strict equality (===) — The strict teacher. Only accepts the exact answer in the exact format. The number 4 and the string "4" are different answers.
  • typeof — Asks “What kind of answer is this?” Is it a number? A string? Something else?
  • Object.is() — The most precise teacher. Even stricter than === — can spot tiny differences that others miss.
TL;DR: Use === for almost everything. Use == null to check for both null and undefined. Use Object.is() only for NaN or -0 edge cases.

Loose Equality (==): The Relaxed Comparison

The == operator tries to be helpful. Before comparing two values, it converts them to the same type. This automatic conversion is called type coercion. For example, if you compare the number 5 with the string "5", JavaScript thinks: “These look similar. Let me convert them and check.” So 5 == "5" returns true.

How It Works

When you write x == y, JavaScript asks:
  1. Are x and y the same type? → Compare them directly
  2. Are they different types? → Convert one or both to match, then compare
This automatic conversion can be helpful, but it can also cause unexpected results.

The Abstract Equality Comparison Algorithm

Here’s the complete algorithm from the ECMAScript specification. When comparing x == y:
1

Same Type?

If x and y are the same type, perform strict equality comparison (===).
2

null and undefined

If x is null and y is undefined (or vice versa), return true.
3

Number and String

If one is a Number and the other is a String, convert the String to a Number.
4

BigInt and String

If one is a BigInt and the other is a String, convert the String to a BigInt.
5

Boolean Conversion

If either value is a Boolean, convert it to a Number (true → 1, false → 0).
6

Object to Primitive

If one is an Object and the other is a String, Number, BigInt, or Symbol, convert the Object to a primitive using ToPrimitive.
7

BigInt and Number

If one is a BigInt and the other is a Number, compare their mathematical values.
8

No Match

If none of the above rules apply, return false.

Visual: The Coercion Decision Tree

The Complete Coercion Rules Table

Here are some comparison results that surprise most developers. Understanding why these happen will help you avoid bugs in your code:

Step-by-Step Trace: [] == ![]

This is one of JavaScript’s most surprising results. An empty array [] equals ![]? Let’s break down why this happens step by step:
1

Evaluate ![]

First, JavaScript evaluates ![].
  • [] is truthy (all objects are truthy)
  • ![] therefore equals false
  • Now we have: [] == false
2

Boolean to Number

One side is a Boolean, so convert it to a Number.
  • false → 0
  • Now we have: [] == 0
3

Object to Primitive

One side is an Object, so convert it via ToPrimitive.
  • [] → "" (empty array’s toString returns empty string)
  • Now we have: "" == 0
4

String to Number

One side is a String and one is a Number, so convert the String.
  • "" → 0 (empty string becomes 0)
  • Now we have: 0 == 0
5

Final Comparison

Both sides are Numbers with the same value.
  • 0 == 0 → true
This example shows why == can produce unexpected results. An empty array appears to equal its own negation! This isn’t a bug. It’s how JavaScript’s conversion rules work. This is why most developers prefer ===.

When == Might Be Useful

Despite its quirks, there’s one legitimate use case for loose equality:
This is equivalent to the more verbose:
Many style guides (including those from Airbnb and StandardJS) make an exception for value == null because it’s a clean way to check for “nullish” values. However, you can also use the nullish coalescing operator (??) or optional chaining (?.) introduced in ES2020.

Strict Equality (===): The Reliable Choice

The strict equality operator compares two values without any conversion. If the types are different, it immediately returns false. This is the operator you should use almost always. It’s simple and predictable: the number 1 and the string "1" are different types, so 1 === "1" returns false. No surprises.

How It Works

When you write x === y, JavaScript asks:
  1. Are x and y the same type? No → return false
  2. Same type? → Compare their values
That’s it. No conversions, no surprises (well, almost. There’s one special case with NaN).

The Strict Equality Comparison Algorithm

1

Type Check

If x and y are different types, return false immediately.
2

Number Comparison

If both are Numbers:
  • If either is NaN, return false
  • If both are the same numeric value, return true
  • +0 and -0 are considered equal
3

String Comparison

If both are Strings, return true if they have the same characters in the same order.
4

Boolean Comparison

If both are Booleans, return true if they’re both true or both false.
5

BigInt Comparison

If both are BigInts, return true if they have the same mathematical value.
6

Symbol Comparison

If both are Symbols, return true only if they are the exact same Symbol.
7

Object Comparison (Reference)

If both are Objects (including Arrays and Functions), return true only if they are the same object (same reference in memory).
8

null and undefined

null === null returns true. undefined === undefined returns true. But null === undefined returns false (different types).

Visual: Strict Equality Flowchart

The Predictable Results

With ===, what you see is what you get:

Special Cases: Two Exceptions to Know

Even === has two edge cases that might surprise you:
Always use Number.isNaN() instead of the global isNaN(). The global isNaN() function converts its argument to a Number first, which means isNaN("hello") returns true. That’s rarely what you want.

Object Comparison: Reference vs Value

This is one of the most important concepts to understand:
To compare objects by their content (deep equality), you need to:
  • Use JSON.stringify() for simple objects (has limitations)
  • Write a recursive comparison function
  • Use a library like Lodash’s _.isEqual()

Object.is(): Same-Value Equality

ES6 introduced Object.is() to fix the two edge cases where === gives unexpected results. As documented by MDN, Object.is() implements the “SameValue” algorithm from the ECMAScript specification. It works exactly like ===, but handles NaN and -0 correctly.

Why It Exists

How It Differs from ===

Object.is() behaves exactly like === except for these two cases:

Complete Comparison Table

When to Use Object.is()

For most everyday code, you won’t need Object.is(). Use === as your default, and reach for Object.is() only when you specifically need to handle NaN or ±0 edge cases.

The typeof Operator

The typeof operator tells you what type a value is. It returns a string like "number", "string", or "boolean". The 2023 State of JS survey found that TypeScript adoption continues to grow — partly driven by developers seeking to avoid the type-checking pitfalls that typeof alone cannot solve. It’s very useful, but it has some famous quirks that surprise many developers.

How It Works

Complete Results Table

The Famous Quirks

Why? This is a bug from JavaScript’s first version in 1995. In the original code, values were stored with a type tag. Objects had the tag 000, and null was represented as 0x00, which also matched the object tag.Why wasn’t it fixed? Too much existing code depends on this behavior. Changing it now would break millions of websites. So we have to work around it.Workaround:
Why? Arrays ARE objects in JavaScript. They inherit from Object.prototype and have special behavior for numeric keys and the length property, but they’re still objects.How to check for arrays:
Use Array.isArray(). It’s the most reliable method.
Why is this different? Functions are technically objects too, but typeof treats them specially because checking for “callable” values is so common. This is actually convenient!
Why? This was a design decision to allow safe feature detection:
In modern JavaScript with modules and bundlers, this pattern is less necessary. But it’s still useful for checking global variables and browser features.
“Not a Number” has a typeof of "number". This sounds strange, but NaN is actually a special value in the number system. It represents a calculation that doesn’t have a valid result, like 0 / 0.

Better Alternatives for Type Checking

Since typeof has limitations, here are more reliable approaches:
These methods from Number are more reliable than typeof for numeric checks.

Decision Guide: Which to Use?

The Simple Rule

Default to === for all comparisons. It’s predictable, doesn’t perform type coercion, and will save you from countless bugs.The only exception: Use == null to check for both null and undefined in one comparison.

Decision Flowchart

Quick Reference

ESLint Configuration

Most style guides enforce === with an exception for null checks:
This allows:

Common Gotchas and Mistakes

These common mistakes trip up many JavaScript developers. Learning about them now will save you debugging time later:
The mistake:
Why it’s wrong: Objects are compared by reference, not by value. Two objects with identical content are still different objects.The fix:
The mistake:
Why it’s confusing: The == operator doesn’t check truthiness. It performs type coercion according to specific rules.The fix:
The mistake:
Why it’s wrong: NaN is never equal to anything, including itself.The fix:
The mistake:
Why it’s wrong: typeof null === "object" is true due to a historical bug.The fix:
The mistake:
Why it’s wrong: String comparison uses lexicographic order (like a dictionary), not numeric value.The fix:
The mistake:
Why it’s confusing: == uses type coercion ([] → "" → 0), but truthiness just checks if the value is truthy (all objects are truthy).The fix:

Common Misconceptions

Not quite! While === should be your default, there’s one legitimate use case for ==:
This is cleaner than checking for both values separately and is explicitly allowed by most style guides (including ESLint’s eqeqeq rule with the "null": "ignore" option).
Partially wrong! === doesn’t just check types. It checks if two values are the same type AND same value.
The key point: === returns false immediately if types differ, then compares values if types match.
Wrong! typeof has several well-known quirks:
Better alternatives:
  • Use Array.isArray() for arrays
  • Use value === null for null
  • Use Number.isNaN() for NaN
  • Use Object.prototype.toString.call() for precise type detection
Wrong! Objects (including arrays and functions) are compared by reference, not by content:
To compare object contents, use JSON.stringify() for simple cases or a deep equality function like Lodash’s _.isEqual().
Misleading! NaN is actually a numeric value that represents an undefined or unrepresentable mathematical result:
Think of NaN as “the result of a calculation that doesn’t produce a meaningful number” rather than literally “not a number.”
Wrong! Truthy/falsy and == equality are completely different concepts:
Rule: Don’t use == true or == false. Either use === or just rely on truthiness directly: if (value).

Key Takeaways

The key things to remember about Equality Operators:
  1. Use === by default — It’s predictable and doesn’t convert types
  2. == converts types first — This leads to unexpected results like "0" == false being true
  3. Only use == for null checks — value == null checks for both null and undefined
  4. NaN !== NaN — NaN doesn’t equal anything, not even itself. Use Number.isNaN() to check for it
  5. Objects compare by reference — {} === {} is false because they’re different objects in memory
  6. typeof null === "object" — This is a bug that can’t be fixed. Always check for null directly
  7. Object.is() for edge cases — Use it when you need to check for NaN or distinguish +0 from -0
  8. Arrays return "object" from typeof — Use Array.isArray() to check for arrays
  9. These rules are commonly asked in interviews — Now you’re prepared!
  10. Configure ESLint — Use the eqeqeq rule to enforce === in your projects

Interactive Visualization Tool

The best way to internalize JavaScript’s equality rules is to see all the comparisons at once.

JavaScript Equality Table

Interactive comparison table by dorey showing the results of == and === for all type combinations. Hover over cells to see explanations. An essential reference for understanding JavaScript equality!
Try these in the table:
  • Compare [] with false, 0, "", and ![] to see why [] == ![] is true
  • See why null == undefined is true but neither equals 0 or false
  • Observe how NaN never equals anything (including itself)
  • Notice how objects only equal themselves (same reference)
Bookmark this table! It’s invaluable for debugging comparison issues and preparing for technical interviews.

Test Your Knowledge

Try to answer each question before revealing the solution:
Answer: trueStep-by-step:
  1. ![] → false (arrays are truthy, so negation makes false)
  2. [] == false → [] == 0 (boolean converts to number)
  3. [] == 0 → "" == 0 (array converts to empty string)
  4. "" == 0 → 0 == 0 (string converts to number)
  5. 0 == 0 → true
This is why understanding type conversion is so important!
Answer: This is a bug from JavaScript’s original implementation in 1995.In the original C code, values were represented with a type tag. Objects had the tag 000, and null was represented as the NULL pointer (0x00), which also matched the 000 tag for objects.This bug was never fixed because too much existing code depends on this behavior. A proposal to fix it was rejected for backward compatibility reasons.Workaround: Always check for null explicitly: value === null
Answer: Use Number.isNaN():
Avoid the global isNaN() because it coerces its argument first:
You can also use Object.is(value, NaN) which returns true for NaN.
Answer: Checking for both null and undefined in a single comparison:
This works because null == undefined is true (special case in the spec), but null and undefined don’t loosely equal anything else (not even 0, "", or false).
Answer: Objects are compared by reference, not by value.When you write {}, JavaScript creates a new object in memory. When you write another {}, it creates a completely different object. Even though they have the same content (both are empty objects), they are stored at different memory locations.
To compare objects by content, you need to compare their properties or use a deep equality function.
Answer: They behave identically except for two edge cases:
Use === for everyday comparisons. Use Object.is() when you specifically need to check for NaN equality or distinguish positive from negative zero.
Answer: Use Array.isArray():
Why not typeof? Because typeof [] === "object". Arrays are objects in JavaScript.Why not instanceof Array? It works in most cases, but can fail across different JavaScript realms (like iframes) where each realm has its own Array constructor.

Frequently Asked Questions

The == (loose equality) operator converts values to the same type before comparing, while === (strict equality) compares both type and value without any conversion. For example, 5 == "5" is true because the string is coerced to a number, but 5 === "5" is false because a number and string are different types. According to the ECMAScript specification, === should be your default choice for reliable comparisons.
This behavior comes from the IEEE 754 floating-point specification, which JavaScript follows. NaN represents an undefined or unrepresentable mathematical result (like 0/0 or Math.sqrt(-1)), so it cannot meaningfully equal any value — including itself. Use Number.isNaN() to check for NaN reliably.
The only widely accepted use case for == is checking for null or undefined in a single expression: if (value == null). This catches both null and undefined because the ECMAScript specification defines null == undefined as true. Most linters, including ESLint’s eqeqeq rule, allow this specific pattern while enforcing === everywhere else.
Object.is() is an ES6 method that implements the “SameValue” equality algorithm. It behaves like === except in two cases: Object.is(NaN, NaN) returns true (unlike ===), and Object.is(+0, -0) returns false (unlike ===). Use it when you need to distinguish positive and negative zero or reliably detect NaN.
This is a well-known bug from JavaScript’s first implementation in 1995. As documented by MDN, values in the original engine used type tags, and both objects and null shared the 000 tag. A TC39 proposal to fix this was rejected because changing it would break backward compatibility with millions of existing websites. Always check for null explicitly with value === null.

Type Coercion

Deep dive into how JavaScript converts between types automatically

Primitive Types

Understanding JavaScript’s fundamental data types

Primitives vs Objects

How primitives and objects behave differently in JavaScript

Scope and Closures

Understanding where variables are accessible in your code

Reference

Equality comparisons and sameness — MDN

Comprehensive official documentation covering ==, ===, Object.is(), and SameValue

typeof — MDN

Official documentation on the typeof operator and its behavior

Articles

JavaScript Double Equals vs. Triple Equals — Brandon Morelli

Uses side-by-side code comparisons to show exactly when == and === produce different results. Great starting point if you’re new to JavaScript equality.

What is the difference between == and === in JavaScript? — Craig Buckler

O’Reilly’s take on the equality debate with a clear recommendation on which operator to default to. Includes the edge cases that trip up even experienced developers.

=== vs == Comparison in JavaScript — FreeCodeCamp

Walks through the type coercion algorithm step-by-step with dozens of examples. The boolean comparison section explains why true == "true" returns false.

Checking Types in Javascript — Toby Ho

Covers the limitations of typeof and when to use instanceof, Object.prototype.toString, or duck typing instead. Includes a reusable type-checking utility function.

How to better check data types in JavaScript — Webbjocke

Provides copy-paste utility functions for checking arrays, objects, nulls, and primitives. Explains why each approach works and when to use which method.

JavaScript Equality Table — dorey

Visual comparison table showing the results of == and === for all type combinations. Essential reference!

Videos

JavaScript "==" VS "===" — Web Dev Simplified

8-minute breakdown with on-screen code examples showing type coercion in action. Kyle’s explanation of the null/undefined special case is particularly helpful.

JavaScript - The typeof operator — Java Brains

Demonstrates the typeof null bug and explains why it exists. Shows how to build a reliable type-checking function that handles all edge cases.

=== vs == in JavaScript — Hitesh Choudhary

Live coding session showing surprising equality results and debugging them in the console. Great for seeing how these operators behave in real development.

== ? === ??? ...#@^% — Shirmung Bielefeld

Conference talk diving deep into JavaScript’s equality quirks and type coercion weirdness.

Books

You Don't Know JS: Types & Grammar — Kyle Simpson

The definitive deep-dive into JavaScript types, coercion, and equality. Free to read online. Essential reading for truly understanding how JavaScript handles comparisons.
Last modified on February 17, 2026