== vs ===.
Coercion or not.
The two equality operators look almost identical. They behave very differently. == converts types before comparing — '5' == 5 is true. === doesn't — '5' === 5 is false. The rule is simple: use === by default. This page covers the coercion rules, the surprising cases, and the one situation where == is acceptable.
Short answer: Use === (strict equality) for everything. It compares value and type without coercion, so '5' === 5 is false. == (loose equality) coerces types first, so '5' == 5 is true — and 0 == '' is also true, which is almost never what you want. The one accepted use of == is value == null to check for both null and undefined in one comparison. Everything else: ===.
✅ The difference in one example
Same values, different result.
console.log('5' == 5); // ?
console.log('5' === 5); // ?
console.log(0 == ''); // ?
console.log(0 === ''); // ?
console.log(null == undefined); // ?
console.log(null === undefined); // ?
console.log(false == '0'); // ?
console.log(false === '0'); // ?
Actual output:
'5' == 5 → true (string coerced to number)
'5' === 5 → false (different types)
0 == '' → true ('' coerced to 0)
0 === '' → false (different types)
null == undefined → true (both "empty")
null === undefined → false (different types)
false == '0' → true ('0' coerced to 0, false to 0)
false === '0' → false (different types)
✅ == coerces. === doesn't.
✅ Every === is false for different types.
✅ Every == can be true if coercion makes them equal.
The last one is the most surprising: false == '0'
is true because both sides become 0. This is the
kind of result that makes == dangerous.
✅ The coercion rules (simplified)
What == actually does.
// When types differ, == converts to a common type:
// String vs Number → string becomes number
'5' == 5 // true ('5' → 5)
'5.5' == 5.5 // true ('5.5' → 5.5)
'abc' == 5 // false ('abc' → NaN, NaN != 5)
// Boolean vs anything → boolean becomes number
true == 1 // true (true → 1)
false == 0 // true (false → 0)
true == '1' // true (true → 1, '1' → 1)
// null and undefined → only equal each other
null == undefined // true
null == 0 // false
undefined == 0 // false
// Object vs primitive → object becomes primitive
[] == '' // true ([] → '')
[] == 0 // true ([] → '' → 0)
[1] == 1 // true ([1] → '1' → 1)
{} == '[object Object]' // true
The spec rules for ==, in order:
1. Same type → compare directly (like ===).
2. null == undefined → true.
3. Number vs String → String becomes Number.
4. Boolean vs anything → Boolean becomes Number.
5. Object vs primitive → Object becomes primitive
(via valueOf or toString).
6. Otherwise → false.
This is why [] == 0 is true:
[] becomes '' (empty string)
'' becomes 0 (number)
0 == 0 → true
And {} == '[object Object]' is true:
{} becomes '[object Object]' (via toString)
These rules are deterministic and specified.
They are also almost never what you want.
=== has one rule: same type AND same value.
That's it. No coercion, no surprises.
The complexity of == is why style guides
recommend banning it entirely.
✅ The one acceptable use of ==
value == null — checking both null and undefined.
// The explicit way — two checks
if (value === null || value === undefined) {
// ...
}
// The shorthand — one check with ==
if (value == null) {
// true if value is null OR undefined
// false for 0, '', false, NaN
}
// This is the ONLY == pattern that's widely accepted.
// It's intentional — you're explicitly using the
// null == undefined rule.
// But some style guides ban == entirely,
// even this one. Both approaches work.
// Know why you're using it.
value == null:
null → true
undefined → true
0 → false
'' → false
false → false
NaN → false
{} → false
[] → false
This is useful for "is this value missing?"
checks, where both null and undefined count
as missing.
It's the one place where == gives you
something === can't — a combined check
for both nullish values.
Some developers use it. Some prefer the
explicit two-check form. Neither is wrong.
The rule: if you use ==, know exactly which
coercion you're relying on. If you can't
explain why it works, use ===.
Don't use == for numbers or strings from user input. This is where == causes the most real bugs. An input field returns a string. input.value == 0 is true when the user types "0", but also when the value is empty (because '' == 0). input.value === 0 is false for both — which is what you want, because the input is a string. Parse first (Number(input.value)), then compare with ===. The same applies to API responses, URL parameters, and anything else that might not be the type you expect. === forces you to be explicit about types.
⚠️ Common mistakes
The equality mistakes that cost real time.
== everywhereCoercion causes surprising results. Fix: use ===.
==Input is a string; == coerces. Fix: parse first, compare with ===.
=== to compare object contentsIt compares references. Fix: JSON.stringify or a deep-equal function.
=== for NaNNaN === NaN is false. Fix: Number.isNaN().
value == null without knowing whyIt works, but only because of the null/undefined rule. Fix: understand it or use two checks.
'' == 0 and '0' == false in conditionsBoth are true — almost always a bug. Fix: ===.
[] == false in filtersAn empty array is truthy in if, but [] == false is true. Fix: use length or ===.
Style guides say === but code still uses ==. Fix: ESLint eqeqeq rule.
❓ Frequently asked
What is the difference between == and === in JavaScript?
== is loose equality — it compares values after converting them to a common type. '5' == 5 is true because the string is coerced to a number. === is strict equality — it compares both value and type, with no coercion. '5' === 5 is false. The rule: use === by default. It does what you expect and catches bugs earlier.
Why does 0 == '' return true?
Because == coerces both sides to a number. The empty string '' becomes 0, so 0 == 0 is true. This is the kind of surprising behavior that makes == dangerous. Other examples: null == undefined is true, false == '0' is true, [] == false is true. Use === to avoid them.
Does === compare object references or values?
For objects, arrays, and functions, === compares references, not contents. Two objects with the same properties are not === unless they point to the same object in memory. To compare object contents, use JSON.stringify or a deep equality check. This applies to both == and ===.
Is it ever okay to use ==?
One case: checking for null or undefined. value == null is true if value is null OR undefined — a shorthand for the two-check version. Some developers use this intentionally. But even this is debatable. If you use value == null, know exactly why. Otherwise, use ===.
Why do style guides say 'always use ==='?
Because == has too many edge cases to hold in your head. Strings become numbers, booleans become numbers, null and undefined only equal each other, objects become '[object Object]'. Every one of these is a potential bug. === has no coercion — it either matches or it doesn't. ESLint's eqeqeq rule flags == by default.
What about NaN? Does === work?
No. NaN === NaN is false. This is a quirk of the IEEE 754 floating-point spec, not JavaScript-specific. To check for NaN, use Number.isNaN(value) (preferred) or the older isNaN(value). Object.is(NaN, NaN) is also true.
🔗 Continue learning
Next in this series: