Equality: ===, == and Object.is
Choose between strict equality, loose equality and Object.is, and understand why two equal-looking objects are not equal.
What you will learn
- Use === by default and explain what == does differently
- Recognise the loose-equality cases that surprise people
- Use Object.is for NaN and negative zero
- Explain why objects and arrays compare by reference
On this page
JavaScript has three ways to ask “are these the same?”, and they disagree in a few cases. This lesson shows the differences and the rule that keeps you out of trouble. The idea of identity versus equality is general, and the fundamentals lesson on records and objects introduces it. Here it takes JavaScript’s form.
Strict equality: ===
a === b is true when the two values have the same type and the same value. There is no conversion. Different types are never strictly equal.
!== is its opposite. Use these two operators by default.
Loose equality: ==
a == b first converts the values to a common type when their types differ, then compares. It follows the coercion rules from the previous lesson, which makes it hard to predict.
console.log("1 === '1' ", 1 === "1");console.log("1 == '1' ", 1 == "1");console.log("0 == '' ", 0 == "");console.log("0 == '0' ", 0 == "0");console.log("'' == '0' ", "" == "0");console.log("false == '0'", false == "0");console.log("null == undefined", null == undefined);console.log("null === undefined", null === undefined);console.log("null == 0 ", null == 0);console.log("[] == false", [] == false);console.log("[1] == 1 ", [1] == 1);console.log("NaN == NaN ", NaN == NaN);1 === '1' false1 == '1' true0 == '' true0 == '0' true'' == '0' falsefalse == '0' truenull == undefined truenull === undefined falsenull == 0 false[] == false true[1] == 1 trueNaN == NaN falseThe results break the rule that equality is transitive: 0 == "" and 0 == "0" are both true, yet "" == "0" is false. [] == false is true, because both sides turn into 0.
The rules are easy to describe but not worth memorising:
- If the types match,
==behaves like===. null == undefinedistrue, and neither equals anything else.- Otherwise, if one side is a number and the other text, boolean or object, they are converted towards numbers.
NaNis not equal to anything, including itself.
One use of == is accepted widely, and it is the check against null: value == null is true for exactly null and undefined, and nothing else.
console.log(null == null, undefined == null);console.log(0 == null, "" == null, false == null, NaN == null);true truefalse false false falseMany teams ban == entirely and write value === null || value === undefined instead. Either is fine, as long as your team agrees. Everywhere else, use ===.
NaN and Object.is
NaN === NaN is false. The test for it is Number.isNaN(value). Object.is(a, b) is a third comparison that behaves like ===, except that it treats NaN as equal to itself and tells 0 from -0.
console.log(NaN === NaN, Object.is(NaN, NaN), Number.isNaN(NaN));console.log(0 === -0, Object.is(0, -0));console.log([NaN].includes(NaN), [NaN].indexOf(NaN));console.log(Object.is("a", "a"), Object.is(1, 1));false true truetrue falsetrue -1true trueNote the last array line: includes finds NaN, while indexOf does not, because indexOf uses ===. You will rarely need Object.is directly, but it is the right tool when NaN or -0 matters.
| Comparison | NaN with NaN |
0 with -0 |
Converts types |
|---|---|---|---|
=== |
false | true | no |
== |
false | true | yes |
Object.is |
true | false | no |
Objects compare by reference
For objects, arrays and functions, === asks whether both sides are the very same object in memory, not whether they look alike. Two separately created arrays with the same items are different objects.
const a = [1, 2];const b = [1, 2];const c = a;
console.log(a === b, a === c);console.log({} === {});
c.push(3);console.log(a);false truefalse[ 1, 2, 3 ]c = a copies the reference, so a and c are one array under two names, and c.push(3) shows up through a. Two literals {} and {} are two objects.
To compare contents, you must write the comparison yourself, such as checking each position:
const x = [1, 2];const y = [1, 2];const z = [2, 1];
console.log(x[0] === y[0] && x[1] === y[1]);console.log(x[0] === z[0] && x[1] === z[1]);truefalseThis only works for two-item lists. Once you know loops and array methods, you can write a comparison that works for any length. For nested data you need to compare recursively. Comparing with JSON.stringify(a) === JSON.stringify(b) is a shortcut that works for plain data only. It depends on key order and breaks on undefined, NaN, and values JSON cannot hold.
Common pitfalls
- Using
==and trusting it."" == 0is true and"" == "0"is false. - Comparing arrays or objects with
===to find out if they hold the same data. They are only equal if they are the same object. - Testing
x === NaN. It is always false. UseNumber.isNaN(x). - Comparing input text with a number.
"5" === 5is false. Convert the input first.
Rewrite each condition so that it does not use ==, and so that it keeps the meaning in brackets.
input == 5, whereinputis the text"5"(meaning: the number five)5 == "5"(meaning: the text"5"equals the number written as text)name == ""(meaning: the name is blank)flag == true(meaning: the flag is on)nothing == null(meaning:nullorundefined)list == false(meaning: the list is empty)Number("abc") == NaN(meaning: the conversion failed)name == 0(meaning: the name is blank, treated as zero)
Input: none. Expected output: eight lines, all true, when the solution’s variables are used.
Show solution
const input = "5";const name = "";const flag = true;const nothing = undefined;const list = [];
console.log(Number(input) === 5);console.log(String(5) === "5");console.log(name === "");console.log(flag);console.log(nothing === null || nothing === undefined);console.log(list.length === 0);console.log(Number.isNaN(Number("abc")));console.log(Number(name) === 0);truetruetruetruetruetruetruetrueIn order: convert the input with Number and compare strictly; convert the number with String; compare with "" directly; use the flag itself, since it is already a boolean; spell out both null cases; test list.length === 0; use Number.isNaN, because NaN never equals anything; and for the last, convert first with Number(name) === 0. Case 8 is a reminder that blank text becomes zero, which is rarely what you want.
Say what each of these five expressions prints, then run them: [] === [], [NaN].includes(NaN), Object.is(0, -0), null == 0, null >= 0.
Input: none. Expected output: false, true, false, false, true.
Show solution
console.log([] === []);console.log([NaN].includes(NaN));console.log(Object.is(0, -0));console.log(null == 0);console.log(null >= 0);falsetruefalsefalsetrue[] === [] is false (two different arrays), [NaN].includes(NaN) is true, Object.is(0, -0) is false, and null == 0 is false.
The last one, null >= 0, is true. The relational operators <, >, <= and >= convert null to 0, while == has a special rule that null equals only undefined. That is why null == 0 is false and null >= 0 is true. It is a good reason to treat null explicitly.
Print the result of each comparison on its own line: "" === 0, "1" == 1, undefined == null, undefined === null, "a" < "b", "B" < "a".
Input: none. Expected output: false, true, true, false, true, true.
Show solution
console.log("" === 0);console.log("1" == 1);console.log(undefined == null);console.log(undefined === null);console.log("a" < "b");console.log("B" < "a");falsetruetruefalsetruetrueStrict equality never converts, so the empty string and 0 differ. Loose equality converts "1" to a number. undefined and null are loosely equal but not strictly equal. Strings compare by character code, and capital letters come before lower-case ones, so "B" < "a" is true.
Summary
- Use
===and!==by default.==converts types first, with results that are hard to predict. The single accepted use isvalue == null. NaNequals nothing, itself included. UseNumber.isNaN.Object.isalso tells0from-0.- Objects and arrays compare by reference. Two arrays with the same items are not
===.
Next: null, undefined, ?? and ?..