JavaScript has two equality operators: == (loose equality) and === (strict equality).
They look similar, but their behavior differs dramatically. This post is about why you should
almost always reach for === — and what happens when you don't.
Always use === and !==. The mental overhead of remembering type coercion rules
isn't worth it. Style guides from Airbnb, Google, and MDN all agree.
1. The Core Difference: Type Coercion
The key distinction is this: == performs type coercion — JavaScript converts operands
to the same type before comparing. === compares both value and type without any conversion.
This single difference causes most surprises. Let's see what happens in practice:
console.log(0 == "0"); // true ← string coerced to number
console.log(0 == false); // true ← false → 0
console.log("" == false); // true ← both become 0
console.log(null == undefined); // true ← special rule
console.log(" \t\n" == 0); // true ← whitespace coerced to 0
// With ===, all of these are false. As expected.
console.log(0 === "0"); // false ✓
console.log(0 === false); // false ✓
console.log(null === undefined);// false ✓
Every one of those == comparisons is technically "true." But are any of them actually equal
in a way that makes sense for your code? Almost certainly not.
2. Real Bugs from the Wild
Here's the thing about type coercion bugs — they're not abstract. They show up in real applications, cause real production issues, and take real hours to debug. Here are three patterns I've seen trip people up:
Bug 1: Form input as a string
Form inputs always return strings. Always. Even type="number" gives you a string until
you parse it. This trips up so many beginners:
// User typed "0" into a text input
let userAge = "0"; // It's a string!
if (userAge == 0) {
console.log("User is newborn or empty input"); // This runs! Bug.
}
// The === version makes the type explicit
if (userAge === 0) {
// This never runs. Which is correct — userAge is a string.
}
// Proper approach: parse first, then compare
let parsedAge = Number(userAge);
if (parsedAge === 0) {
console.log("Age is zero"); // Now intentional
}
Bug 2: The NaN trap
NaN is one of JavaScript's weird corners. It doesn't equal itself — not even with ===.
And with ==, the confusion doubles:
let result = parseInt("abc"); // Returns NaN
if (result == NaN) { // false — NaN != NaN!
// This never runs — silent bug
}
if (result === NaN) { // Also false! NaN !== NaN
// Still never runs
}
// Correct ways to check for NaN:
if (isNaN(result)) { ... } // true ✓ (but also coerces non-numbers)
if (Number.isNaN(result)) { ... } // true ✓ (strict, no coercion)
Use Number.isNaN() over isNaN(). The global isNaN() coerces its argument first,
so isNaN("hello") returns true. Number.isNaN("hello") correctly returns false.
Bug 3: Falsy value surprises
JavaScript has six falsy values: false, 0, "", null, undefined, and NaN.
With ==, many of these are considered equal to each other — which is almost never what you want:
let quantity = ""; // Empty form field
if (quantity == 0) {
console.log("Out of stock"); // Runs! Because "" == 0
}
// With ===:
if (quantity === 0) {
// Doesn't run. "" is not 0. Correct behavior.
}
// Even better — check for what you actually mean:
if (quantity === "" || quantity === undefined) {
console.log("No quantity provided");
}
if (Number(quantity) === 0) {
console.log("Quantity is zero");
}
3. The Mental Model Problem
Even if you know all the coercion rules — and there are many — using == forces anyone
reading your code (including future you) to mentally simulate type coercion to understand what's happening.
That's cognitive overhead that buys you nothing.
=== is explicit. It says: "I want these to be exactly equal, same type, same value."
There's no ambiguity, no hidden rules, no surprises.
4. What the Experts Say
This isn't just my opinion. Here's what major style guides recommend:
// ✗ bad
if (a == b) { ... }
// ✓ good
if (a === b) { ... }
MDN documentation consistently recommends strict equality. ESLint's eqeqeq rule (which most
teams enable) enforces this automatically. Kyle Simpson in "You Don't Know JS" explains the coercion
rules exhaustively — and even he recommends === when in doubt.
5. The Legitimate Uses of ==
There's one common pattern where == is actually useful — checking for null or undefined simultaneously:
// This checks for both null AND undefined
if (value == null) {
console.log("value is null or undefined");
}
// Equivalent to:
if (value === null || value === undefined) { ... }
// The == version is shorter, and the intent is clear to experienced devs.
// But I'd still recommend the explicit === version in most codebases
// for consistency and readability.
Summary
The rule is simple: always use === and !==.
The one exception (== null) is narrow and optional — and worth the tradeoff of strict consistency.
Type coercion with == isn't just confusing — it actively hides bugs. Bugs that are hard to
spot, hard to trace, and completely avoidable. The extra character is worth it every time.
I spent 4 hours once debugging a comparison issue in a form handler. It was ==.
That was the day I added ESLint's eqeqeq rule to every project I start.