Equality & sameness
Compare JavaScript values with confidence by learning ==, ===, Object.is, and SameValueZero step by step.
- 01Choose the right judgeKnow when ===, ==, Object.is, includes, Map, and Set use different sameness rules.
- 02Trace loose equalityWalk the spec-shaped == algorithm instead of memorizing a giant table.
- 03Handle NaN and -0Recognize the two numeric edge cases that explain most surprising equality results.
Four sameness judges
JavaScript does not have one idea called equality. It has several sameness algorithms, each built for a different job. Earlier comparison lessons introduced ===, ==, NaN, and the null/undefined quirk. This lesson formalizes the rulebooks so you can predict them instead of memorizing a giant matrix.
Imagine four judges deciding whether two badges belong to the same person. One refuses translations, one allows them, one uses a microscope, and one checks a guest list. Give them the same pair of values and their verdicts can differ because their jobs differ.
- In real life: A strict judge checks the ID exactly
- In JavaScript:
===requires the same type and same value - In real life: A translator judge lets people convert papers first
- In JavaScript:
==converts, then compares - In real life: A microscope judge notices tiny details
- In JavaScript:
Object.isnoticesNaNand the sign of zero - In real life: A guest-list judge is practical
- In JavaScript: SameValueZero powers
includes,Map, andSet
Where the analogy stops: The judges are not people choosing opinions. They are deterministic algorithms in the language specification. The analogy helps you remember which details each one cares about.
The names you will see are IsStrictlyEqual for ===, IsLooselyEqual for ==, SameValue for Object.is, and SameValueZero for includes, Map, and Set. SameValueZero is SameValue except that it treats -0 and +0 as the same.
| Algorithm | Where you meet it | Coerces? | NaN equals NaN? | -0 equals +0? |
|---|---|---|---|---|
| Strict equality | === and !== | No | No | Yes |
| Loose equality | == and != | Yes | No | Yes |
| SameValue | Object.is | No | Yes | No |
| SameValueZero | includes, Map, Set | No | Yes | Yes |
Strict equality: same type, same value
INTERACTIVEStrict equality is your default judge. It does not convert either side. If the types differ, the answer is immediately false. For primitives of the same type, it compares the value. For objects, arrays, and functions, the value is the reference: whether both sides point at the same object.
const shared = { id: 1 };const alsoShared = shared;const twin = { id: 1 }; console.log(1 === "1");console.log(shared === alsoShared);console.log(shared === twin);console.log(NaN === NaN);console.log(-0 === +0);The two historical exceptions are the ones professionals still trip over: NaN === NaN is false, and -0 === +0 is true. Use the playground to ask all four judges about those cases, plus strings, numbers, BigInts, arrays, and object references.
const a = NaN;const b = NaN;console.log(a === b);console.log(a == b);console.log(Object.is(a, b));console.log([a].includes(b));Strict equality has the historical rule that NaN is not equal to itself.
Same type, so == delegates to strict equality.
SameValue says NaN is the same NaN value.
SameValueZero matches NaN with NaN.
You selected NaN vs NaN. The verdicts below are computed with real JavaScript operators and collection membership.
Use === unless you can name the conversion you want. It keeps the question local: type and value match, or they do not.
The loose equality algorithm
STEP THROUGHLoose equality is not random. It is an algorithm that repeatedly asks, “Do these types already match? If not, is there a conversion rule for this pair?” The previous Type coercion lesson introduced ToPrimitive, ToNumber, and ToBoolean; == is one place those operations become visible.
To compare two prices, first put both in rupees. Loose equality does the same kind of work: it may convert values before comparing them.
- In real life: Prices already in rupees
- In JavaScript: Same type: use strict equality
- In real life: Two missing price labels
- In JavaScript:
null == undefined - In real life: Convert a price to rupees first
- In JavaScript: String, boolean, or object conversion
- In real life: No price can be compared
- In JavaScript: No matching rule means
false
Where the analogy stops: Real prices have currencies and exchange rates. JavaScript follows fixed conversion rules, then makes the final strict comparison.
The useful skeleton is:
- Same type? Use strict equality.
nullandundefined? They equal each other.- Number and string? Convert the string with
ToNumber. - Boolean? Convert it to
1or0first. - Object and primitive? Convert the object with
ToPrimitive. - BigInt with string or number? Use the special safe rules.
- No rule? The result is false.
Walk the loose-equality rulebook. The replay mirrors the spec branches for the chosen pair and checks the final result against real JavaScript ==.
script
// 1. If types match, use strict equality.// 2. null and undefined equal only each other.// 3. Strings, booleans, and objects may convert.// 4. BigInt has careful string/number rules.// 5. If no rule matches, return false.value == null is a deliberate shorthand for value === null || value === undefined. It does not match false, 0, or an empty string.
Object.is, NaN & -0
STEP THROUGHObject.is(a, b) exposes the SameValue algorithm. It does not coerce, object references still matter, and it differs from === in exactly two numeric places: Object.is(NaN, NaN) is true, while Object.is(-0, +0) is false.
Step through the two values strict equality handles oddly: NaN and negative zero.
script
console.log(rounded);console.log(1 / rounded);console.log(Object.is(rounded, -0));console.log(JSON.parse(JSON.stringify(rounded)));console.log(Number.isNaN(NaN));console.log(NaN !== NaN);Negative zero can appear from calculations such as Math.round(-0.4) or -1 * 0. Most UI text does not care, but numeric code sometimes does because 1 / -0 gives -Infinity. A JSON round trip erases the sign: JSON.stringify(-0) becomes "0".
Prefer Number.isNaN(value). The old trick value !== value also detects NaN, because no other JavaScript value is unequal to itself, but it reads like a riddle.
SameValueZero in includes & Map
INTERACTIVESameValueZero is the practical collection judge. It is like Object.is for NaN, but like === for signed zero. That is why [NaN].includes(NaN) is true, [NaN].indexOf(NaN) is -1, and new Set([-0]).has(0) is true.
See why includes, Map, and Set feel friendlier than indexOf around NaN and zero signs.
script
console.log([NaN].indexOf(NaN));const map = new Map([[NaN, "stored"]]);console.log(map.get(NaN));const set = new Set([-0]);console.log(set.has(0));console.log([NaN].includes(NaN));console.log([NaN].indexOf(NaN));const map = new Map([[NaN, "stored"]]);console.log(map.get(NaN));const set = new Set([-0]);console.log(set.has(0));new Map([[NaN, "stored"]]).get(NaN) returns stored.
Map keys use SameValueZero, so a NaN key can be retrieved with another NaN.
| Pair | === | == | Object.is | SameValueZero |
|---|---|---|---|---|
NaN / NaN | false | false | true | true |
-0 / +0 | true | true | false | true |
"1" / 1 | false | true | false | false |
null / undefined | false | true | false | false |
Where you'll use this
Most real code uses the same small set of choices. Use strict equality for ordinary comparisons. Use value == null when you intentionally mean “missing as null or undefined.” Use Object.is when your logic is sensitive to NaN or the sign of zero. React uses Object.is for some state comparisons, which is why this detail occasionally appears in UI debugging.
if (user.middleName == null) { showOptionalNameField();} if (Object.is(nextTemperature, previousTemperature)) { skipChartUpdate();} const uniqueIds = new Set(idsFromSeveralLists);Collections do another job for you. If you dedupe values with new Set(values), it uses SameValueZero, so duplicate NaN values collapse and -0/+0 count as one value.
Common misconceptions
“Strict equality means mathematical equality.”
It is close for normal numbers, but NaN and signed zero follow JavaScript's strict-equality rules, not school math.
“Loose equality is unpredictable.”
It is predictable, but the ordered conversions are too easy to misread. Trace the algorithm, or avoid it except for clear patterns.
“Objects with the same shape are equal.”
Two object literals can look identical and still be different references. Equality does not do a deep property comparison.
“Object.is is always stricter ===.”
It is pickier about signed zero and friendlier about NaN. It is different, not simply stricter.
“includes and indexOf search the same way.”
They differ for NaN: includes uses SameValueZero; indexOf uses strict equality.
- "1" and 1 are true
- null and undefined are true
- NaN and NaN are true
[NaN].includes(NaN) is true- -0 and +0 are true
new Set([-0]).has(0) is true- obj and obj are true without conversion
Object.is(-0, +0) is false
Sort each statement by the rule that explains why it is true. Some statements are true under more than one judge; choose the judge named by the card's context.
Practice: equality & sameness
5 EXERCISESPredict the three printed booleans, in order.
console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log([NaN].includes(NaN));The output is false true true. Strict equality says NaN is not equal to itself. Object.is and SameValueZero both say NaN matches NaN.
"0" == falseWrite the final printed value, then explain the conversion chain to yourself.
console.log("0" == false);false becomes 0, so the question restarts as "0" == 0. String/number comparison converts "0" to 0, and 0 === 0 is true.
Replace the body so the function identifies real NaN values without treating the string "NaN" as NaN.
function isActuallyNaN(value) {
return value === NaN;
}function isActuallyNaN(value) {
return Number.isNaN(value);
}
console.log(isActuallyNaN(NaN));
console.log(isActuallyNaN("NaN"));Number.isNaN(NaN) is true and Number.isNaN("NaN") is false. That is usually the check you want.
Predict the first printed value, then explain why the unique list still includes NaN.
const values = [NaN, NaN, -0, 0, "0", "0"];
const unique = [...new Set(values)];
console.log(unique.length);
console.log(unique.includes(NaN));The unique list has three members: one NaN, one zero, and one string "0". SameValueZero is why the NaN values collapse too.
In one sentence, explain why Object.is(-0, 0) is false even though -0 === 0 is true.
console.log(Object.is(-0, 0));
console.log(1 / -0);Object.is(-0, 0) is false because SameValue keeps the sign of zero. You can observe that sign with 1 / -0, which produces -Infinity.
Quiz: check your understanding
8 QUESTIONSAnswer from the rulebook, not from vibes. Every option explains itself.
Question 1 of 8Which equality check should be your default in ordinary application code?
Choose an answer to see the explanation.
Question 2 of 8What does
"1" == 1print?Read the code, then predictconsole.log("1" == 1);Choose an answer to see the explanation.
Question 3 of 8What does
NaN === NaNprint?Read the code, then predictconsole.log(NaN === NaN);Choose an answer to see the explanation.
Question 4 of 8What does
Object.is(-0, 0)print?Read the code, then predictconsole.log(Object.is(-0, 0));Choose an answer to see the explanation.
Question 5 of 8Which operation uses SameValueZero?
Choose an answer to see the explanation.
Question 6 of 8What does
null == undefinedprint?Read the code, then predictconsole.log(null == undefined);Choose an answer to see the explanation.
Question 7 of 8What does
[NaN].indexOf(NaN)print?Read the code, then predictconsole.log([NaN].indexOf(NaN));Choose an answer to see the explanation.
Question 8 of 8Which expression is a good intentional use of loose equality?
Choose an answer to see the explanation.
Key takeaways
- Default to
===: same type, same value, no conversion. ==follows an ordered conversion algorithm; use it deliberately, especially forvalue == null.Object.isis SameValue:NaNmatches itself, and-0differs from+0.- SameValueZero powers
includes,Map, andSet:NaNmatches and zero signs merge. - Object equality is reference equality, not a deep comparison of properties.
Remember the one-liner.
JavaScript has four sameness judges: strict avoids conversion, loose converts, Object.is notices NaN and signed zero, and SameValueZero powers collections.
Up next: Checking types reliably.