AlgoInsight

Free lesson · JavaScript Basic to Advance

Equality: == vs ===

Easy5 min read

Two Ways to Compare: One Safe, One Dangerous

JavaScript has two equality operators, and they don't do the same thing.

console.log(5 === 5);        // true
console.log(5 === "5");      // false — different types
console.log(5 == "5");       // true — coerces "5" to number 5 first
console.log(true === 1);     // false — different types
console.log(true == 1);      // true — coerces true to 1
console.log(null === undefined);  // false
console.log(null == undefined);   // true — special-case rule

The strict version is straightforward. The loose version is where JavaScript developers spend hours of debugging time wondering why their comparisons produce unexpected results.

The short version of this Topic: use === almost always. Avoid == except in one narrow case. Now let me explain why.

How === Works

Strict equality is the simple operator. It checks:

  1. Are the two values the same type?
  2. If yes, are they the same value?

If both answers are yes, the result is true. Otherwise, false. No conversions happen. No surprises.

console.log(5 === 5);            // true
console.log(5 === 6);            // false
console.log("hello" === "hello");// true
console.log("hello" === "world");// false
console.log(true === true);      // true
console.log(null === null);      // true
console.log(undefined === undefined); // true

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

The rule is so simple that it produces no surprises. If you're not sure what an === comparison will return, you almost certainly know the answer once you check whether the types match.

How == Works (and Why It's Confusing)

Loose equality coerces values to a common type before comparing them. The coercion rules are complex enough that there are dedicated charts on the internet to look them up. Here's the simplified version:

graph TD
    A[a == b] --> B{Same type?}
    B -->|Yes| C[Compare directly]
    B -->|No| D{Is one null and<br/>the other undefined?}
    D -->|Yes| E[Return true]
    D -->|No| F{Is one a number<br/>and the other a string?}
    F -->|Yes| G[Coerce string to number,<br/>then compare]
    F -->|No| H{Is one a boolean?}
    H -->|Yes| I[Coerce boolean to number,<br/>then compare]
    H -->|No| J{Is one an object<br/>and the other primitive?}
    J -->|Yes| K[Coerce object to primitive,<br/>then compare]
    J -->|No| L[Return false]

    style E fill:#7c3aed,color:#fff
    style G fill:#7c3aed,color:#fff
    style I fill:#7c3aed,color:#fff
    style K fill:#7c3aed,color:#fff
This diagram shows the decision tree JavaScript follows when evaluating loose equality with the double-equals operator. First it checks if the two values are the same type, in which case it compares them directly. If not, it goes through a series of special cases: null and undefined are considered equal to each other. Numbers and strings are compared by coercing the string to a number. Booleans are coerced to numbers. Objects compared with primitives are coerced to primitives. If none of these special cases apply, the result is false.

Look at the size of that decision tree. That's why == is dangerous — there are many paths it can take, and the path it picks depends on the types of both operands.

Some of the consequences:

console.log(0 == false);         // true (false becomes 0)
console.log(0 == "");            // true (both become 0)
console.log(0 == "0");           // true ("0" becomes 0)
console.log(false == "");        // true (both become 0)
console.log(false == "0");       // true (both become 0)
console.log("" == "0");          // false (both strings, compared directly)
console.log(null == 0);          // false (special rule: null only equals null/undefined)
console.log(null == undefined);  // true (the famous special case)
console.log(null == false);      // false (null doesn't coerce to 0 for this comparison)

Look at that carefully. 0 == false is true. 0 == "" is true. false == "" is true. But "" == "0" is false. The transitive property — if A equals B and B equals C, then A equals C — doesn't hold. This is one of the most-cited reasons why == is considered a design flaw.

Interactive animation: What == actually decides for six comparisons - including the three rows that show it is not transitive.

The Famous == Trap

Here's the example that gets shared most often as a JavaScript joke:

console.log([] == false);        // true
console.log([] == 0);            // true
console.log([] == "");           // true
console.log([] == ![]);          // true — yes, really

That last one — [] == ![] — is mind-bending. Let me trace it:

  1. ![] is evaluated first. An empty array is truthy. !truthy is false. So ![] is false.
  2. Now we have [] == false.
  3. false coerces to 0.
  4. [] coerces to "" (an empty array becomes an empty string).
  5. "" coerces to 0 (because we're comparing with a number).
  6. 0 == 0 is true.

So [] == ![] produces true through five separate coercions. With ===, this comparison would be false and there would be nothing to think about.

When == Is Acceptable

There is exactly one place where many style guides allow ==, and it's the comparison with null:

function isEmpty(value) {
  return value == null;   // true if value is null OR undefined
}

console.log(isEmpty(null));       // true
console.log(isEmpty(undefined));  // true
console.log(isEmpty(0));          // false
console.log(isEmpty(""));         // false
console.log(isEmpty(false));      // false

The expression value == null catches both null and undefined and nothing else. This is the special case from the decision tree — null and undefined are loosely equal to each other but to nothing else.

Whether to use this shortcut is a style decision. Some teams allow it; others ban == entirely and require the more verbose value === null || value === undefined. Both positions are defensible.

Interactive animation: The one time to reach for == - x == null means null or undefined, and nothing else.

The Rule: Use === Unless You Have a Specific Reason

For nearly every comparison in your code, === is the right choice. It does what you expect. It doesn't trigger coercion. It doesn't have a decision tree. If the comparison feels surprising, the surprise is honest — the types really are different.

// Good
if (userInput === "admin") { /* ... */ }
if (count === 0) { /* ... */ }
if (status === null) { /* ... */ }

// Suspicious — why are we using loose equality?
if (userInput == "admin") { /* ... */ }
if (count == 0) { /* ... */ }

// Allowed by some style guides (catches null + undefined)
if (value == null) { /* ... */ }

When you read JavaScript code in production codebases, you'll see almost all comparisons use ===. The few == you see are either deliberate (the null case) or bugs waiting to happen.

Common Pitfalls

Warning: The three traps developers fall into with equality.

Forgetting that NaN !== NaN. Both equality operators consider NaN not equal to itself. To check for NaN, use Number.isNaN(value). Topic 2.3 covered this in detail.

Using == "to be flexible." Beginners sometimes use == because they think strict equality is "too strict." It isn't. The strictness is the feature. Loose equality is "flexible" in the same way that an unreliable car is "interesting."

Comparing objects with ===. Two objects are equal only if they're literally the same object in memory, not if they have the same contents. {a: 1} === {a: 1} is false. This is true for === and == both. To compare object contents, you need to compare property-by-property or use a deep-equality function.

Interactive animation: Two objects with the same contents compared with == and === - equality asks whether it is the same object.

Why This Matters in Interviews

Interview: "What's the difference between == and ===?" is one of the top three most-asked JavaScript interview questions. The expected answer has three parts:

  1. === compares value and type, without coercion. == coerces operands to a common type before comparing.
  2. === is preferred in nearly all cases because its behavior is predictable.
  3. The one common exception: value == null catches both null and undefined, which some style guides allow as a shortcut.

A candidate who can list those three points has signaled they understand JavaScript at depth. A candidate who says "they're basically the same" or "use == to be less strict" has signaled the opposite.

Topic 2.8 — Interview Lens: Type Coercion Questions — covers the full interview-prep treatment of equality questions, including the specific puzzles interviewers love to ask.

Try It

Exercise: Predict each result. Then run to check.

0 === false
0 == false
"" == 0
"0" == 0
"0" == ""
null === undefined
null == undefined
null == 0
null == false
NaN === NaN
NaN == NaN
[] == []
[] == false
[1, 2] == "1,2"

Three of these results will surprise you the first time. Identify which three. For each, trace through the coercion rules to understand why JavaScript produces that answer. Once you can trace them, you've internalized the equality system.

Back to JavaScript Basic to Advance