JAVASCRIPT / VALUES, TYPES, AND COERCION
Loose equality against strict equality
Predict the result of any == or === comparison by tracking the one conversion step that == adds, and know when each operator is the right tool.
What you will learn
- Predict any == result by naming which operand converts and what it converts to
- Explain why '0' == 0 and 0 == '' are true while '0' == '' is false
- Replace coercion-dependent comparisons with Number() or String() plus ===
- Use x == null as the one deliberate ==: true for null and undefined only
Understanding Loose equality against strict equality
Both operators ask whether two values are the same, and they differ in exactly one thing: what happens when the operands have different types. === checks the types first, so string against number is a mismatch, the answer is false, and the values themselves are never inspected. == instead runs a short conversion routine to force both sides into a common type, and then compares the converted values in precisely the way === would. So == is not a fuzzy or approximate comparison; it is === with a documented preamble.
That preamble is small enough to memorise. If the operands already share a type, == does nothing extra and behaves exactly like ===; null == undefined is defined as true, and neither of them is == to anything else, not even 0 or ''. Otherwise a boolean operand is turned into 0 or 1, a string compared against a number goes through the same conversion Number() performs, and an object is reduced to a primitive with valueOf then toString before the comparison restarts with the result. Almost every path points at numbers, which is why '' == 0 and [1] == 1 both come out true.
The price of that conversion is that == throws away the information you need in order to reason about the comparison. '0' == 0 and 0 == '' are true while '0' == '' is false, so == is not transitive, and its result cannot be read off the line alone: you have to know the runtime type of both operands. Because the two operators agree whenever the types already match, writing === costs nothing and turns a type mismatch into a visible false instead of a silent conversion. Convert at the point where the types get mixed, then compare strictly.
// == and === differ only when the two operands have different types.
console.log("1 == '1' ->", 1 == '1');
console.log("1 === '1' ->", 1 === '1');
console.log("true == 1 ->", true == 1);
console.log("true === 1 ->", true === 1);
console.log("'' == 0 ->", '' == 0);
console.log("'' === 0 ->", '' === 0);
console.log("[1] == 1 ->", [1] == 1);
console.log("[1] === 1 ->", [1] === 1);== applies a fixed table of type conversions and then does exactly what === does, so the two can only disagree when the operands have different types.
Worked examples
Why == is not transitive
Three values where two == comparisons succeed but the third fails, because conversion only happens on mixed types.
const a = '0';
const b = 0;
const c = '';
console.log("a == b ->", a == b);
console.log("b == c ->", b == c);
console.log("a == c ->", a == c);Example explained
Line 1a == b mixes string and number, so '0' is converted to the number 0 and 0 == 0 holds.
Line 2b == c mixes number and string, so '' is converted and also becomes 0.
Line 3a == c has two strings, so == skips conversion and compares characters: one character against none.
Line 4Equality that depends on conversion cannot be chained, which is why == results never compose the way === results do.
Objects compare by identity
Shows that == does not inspect object contents, and what it does instead when only one side is an object.
const p = { size: 10 };
const q = { size: 10 };
const list = [10];
console.log("p == q ->", p == q);
console.log("p == p ->", p == p);
console.log("list == '10' ->", list == '10');
console.log("list == 10 ->", list == 10);
console.log("list === '10' ->", list === '10');Example explained
Line 1p and q are both objects, so the types match, no conversion runs, and == compares references: matching properties are irrelevant.
Line 2p == p is true only because it is the same reference, which is the sole way two objects compare equal.
Line 3With an object on one side and a primitive on the other, == reduces the array to the primitive '10' and restarts, so it matches both '10' and 10.
Line 4=== refuses at the type check, so list === '10' is false even though list == '10' is true.
Comparing input strings to numbers
The realistic case where types disagree, and why converting explicitly beats switching to ==.
const raw = '42'; // values from inputs and JSON arrive as strings
console.log("raw === 42 ->", raw === 42);
console.log("Number(raw) === 42 ->", Number(raw) === 42);
console.log("raw == 42 ->", raw == 42);
console.log("'0x2a' == 42 ->", '0x2a' == 42);
function label(value) {
return value == null ? 'missing' : value;
}
console.log("label(undefined) ->", label(undefined));
console.log("label(null) ->", label(null));
console.log("label(0) ->", label(0));Example explained
Line 1raw === 42 is false because raw is a string; === stops at the type check and never looks at the digits.
Line 2Number(raw) === 42 does the conversion once, in code you can see, and leaves the comparison strict.
Line 3'0x2a' == 42 is true as well, because the string-to-number step accepts hex literals and surrounding whitespace: that is the precision you hand over to ==.
Line 4value == null is the one deliberate use of ==, matching null and undefined and nothing else, so 0 comes back unchanged.
Important notes
Neither operator makes NaN equal to itself, and both report -0 == +0 as true; those are value-level quirks, so switching operators changes nothing and Object.is is the tool that distinguishes them.
An == comparison with an object operand can run your own valueOf or toString, so its result is not always predictable from the comparison line; === never calls anything.
Common mistakes
Fixing a failing input.value === 5 by changing it to == instead of Number(input.value) === 5, which then also accepts ' 5 ' and '0x5', so bad input passes validation.
Testing for a missing value with x == false: it is true for 0, '', [] and '0' but false for null and undefined, so the branch fires for the exact values it was meant to let through.
Expecting == to compare object contents, so { id: 1 } == { id: 1 } is false and lookup or de-duplication code silently never finds a match.
Try it yourself
Change, predict, then run
In a browser console, evaluate all six == comparisons between 0, '', '0' and [], and next to each true result write a comment naming the operand that was converted and the value it converted to. Then run the same six pairs with === and count how many are true.
Open the JavaScript workspaceCheck your understanding
You find a comparison where x == y is true but x === y is false. What must be true of x and y?
- They are two different objects with the same properties
- One of them is NaN
- They have different types
- They are strings that differ only in leading or trailing whitespace
Show answer
Once the operands share a type, == performs the same comparison as ===, so the two can only disagree after == has converted one side, which happens only when the types differ. Two objects with matching properties is the tempting answer, but both have the same type, so == compares references and returns false exactly like ===; NaN is also false under both.