Loose (==) vs strict (===) equality in JavaScript
== is loose equality, which first coerces both operands to a common type, while === is strict equality, which demands the same type and the same value. One extra character changes the result: 0 == false is true, but 0 === false is false.
Theory
TL;DR
==converts types before comparing,===converts nothing.- With
===, atrueresult is only possible when type and value both match. null == undefinedistrue, butnull === undefinedisfalse.- The coercion rules behind
==are not obvious, which is exactly where bugs with user input or API data come from. - The de facto standard:
===and!==everywhere, and==only in the formvalue == null.
Quick example
console.log(5 == '5'); // true, the types were coerced
console.log(5 === '5'); // false, number versus string
console.log(0 == false); // true, false becomes 0
console.log(0 === false); // false, number versus booleanThe == operator, loose (abstract) comparison
== converts the types of the operands before comparing them. In other words, JavaScript tries to "guess" whether the values are equal after type coercion.
Examples:
5 == '5' // true -> the string '5' turns into the number 5
0 == false // true -> false is coerced to 0
null == undefined // true -> this is the only case where they are equal
' ' == 0 // true -> a whitespace string turns into 0The danger: implicit conversions are a frequent source of bugs, especially when checking user input or data coming from an API.
The === operator, strict comparison
=== performs no type conversion. The operands must have the same type and the same value for the result to be true.
Examples:
5 === '5' // false -> different types (number and string)
0 === false // false -> different types
null === undefined // false -> different types
5 === 5 // true -> same value and same typeHow == actually coerces
The list of rules is short, yet almost nobody remembers it by heart, and that is the main argument against ==:
- If the types match, plain strict comparison runs.
nullandundefinedare equal to each other and to nothing else.- Number versus string: the string is converted to a number.
- A boolean on either side becomes a number first (
truegives 1,falsegives 0). - Object versus primitive: the object is reduced to a primitive via
valueOf()ortoString().
[] == false; // true, [] becomes '', then 0, and false is 0 too
[1] == 1; // true, [1] becomes '1', then 1
'0' == false; // true, both are reduced to 0
'0' === false; // falseNeither operator helps in two special cases: NaN is not equal to itself, and +0 and -0 count as equal. Object.is() exists for exactly those situations.
NaN === NaN; // false
Object.is(NaN, NaN); // true
Object.is(0, -0); // falseSummary: when to use which
| Operator | Coerces types | Typical use |
|---|---|---|
=== / !== | no | almost always, the default for any check |
== / != | yes | only deliberately, as value == null |
Object.is() | no | when NaN or the sign of zero must be distinguished |
Recommendation: always use === (and !==) unless there is a specific reason for loose comparison. It makes the code predictable and cuts down the number of bugs. The one exception that even strict linters allow is if (value == null), because it covers both null and undefined in a single short check.
Common mistakes
- Checking form or API data with
==. Input always arrives as a string, so'0' == falseistrueand the condition fires in a way nobody intended. - Assuming
===compares object contents. For objects both operators compare references:{} === {}isfalse. - Writing
x == NaN. No comparison againstNaNever returnstrue, you needNumber.isNaN(x). - Using
== nullwithout understanding it. It is a deliberate check fornullandundefinedtogether, not a "less strict" emptiness check:0and''do not pass it. - Confusing
==with=. A single sign is an assignment inside the condition, and it is always truthy for a non-zero value.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.