completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Equality & sameness

Compare JavaScript values with confidence by learning ==, ===, Object.is, and SameValueZero step by step.

By the end you can
  • 01
    Pick the right checkKnow which set of rules ===, ==, Object.is, includes, Map, and Set each use, and when their answers differ.
  • 02
    Follow loose equality step by stepWalk through the steps of == in order, instead of memorizing a big table.
  • 03
    Handle NaN and -0Spot the two number cases that explain most surprising equality results.

Four ways to check sameness

When you compare two values, you ask one question: are these the same? JavaScript has four different sets of rules for answering it. Each set of rules is a fixed list of steps. The specification, the official rulebook for JavaScript, calls such a list an algorithm.

You met ===, ==, NaN, and the null/undefined rule in earlier lessons. This lesson puts all four sets of rules side by side. Then you can predict each answer instead of memorizing a big table.

Real-life analogyFour judges, four rulebooks

Picture four judges at a door. Each one decides whether two ID badges belong to the same person. The first judge accepts only an exact match. The second judge translates the badges first. The third judge uses a microscope. The fourth judge checks a guest list. Give them the same two badges, and they can give different answers, because each one follows a different rulebook.

In real life: A strict judge accepts only an exact match
In JavaScript: ===: same type and same value, nothing changed
In real life: A translator judge translates the badges first
In JavaScript: ==: may convert the values, then compares
In real life: A microscope judge notices tiny details
In JavaScript: Object.is: notices NaN and the minus sign on -0
In real life: A guest-list judge checks if a name is on the list
In JavaScript: SameValueZero: used by includes, Map, and Set

The specification gives each set of rules a name:

  • IsStrictlyEqual is used by ===.
  • IsLooselyEqual is used by ==.
  • SameValue is used by Object.is.
  • SameValueZero is used by includes, Map, and Set.

SameValueZero is SameValue with one change: it counts -0 (negative zero) and +0 (positive zero) as the same.

The table below compares the four. Read each row as: "these rules, used here, give these answers."

The four sets of rules side by side
RulesWhere you use themConverts values first?Is NaN the same as NaN?Is -0 the same as +0?
Strict equality=== and !==NoNoYes
Loose equality== and !=YesNoYes
SameValueObject.isNoYesNo
SameValueZeroincludes, Map, SetNoYesYes

Strict equality: same type, same value

INTERACTIVE

Strict equality, written ===, is the one to use by default. It never converts either value. It checks like this:

  • Are both values the same type? If not, the answer is false right away.
  • Are they primitives, the simple values like numbers, strings, and booleans? Then it checks that the values are the same.
  • Are they objects, arrays, or functions? Then it checks that both sides point to the very same object.

A variable never holds an object itself. It holds a reference: a note that says where the object lives. So for objects, === compares the two references.

Start with ordinary valuesPop out in the code editor (opens in a new tab)JavaScript
const user = "Asha";const sameName = "Asha"; console.log(user === sameName);console.log(3 === "3");console.log(true === 1);

Line 4 prints true, because both variables hold the same string, "Asha". Line 5 prints false, because the string "3" and the number 3 are different types. Line 6 also prints false. true is a boolean and 1 is a number, so they are different types too.

Five strict checks, two surprisesPop out in the code editor (opens in a new tab)JavaScript
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);

Lines 5 to 7 follow the rules above:

  • Line 5 prints false: a number and a string are different types.
  • Line 6 prints true: shared and alsoShared point to the same object.
  • Line 7 prints false: twin looks the same, but it is a separate object.

Lines 8 and 9 are the two surprises:

  • Line 8 prints false. NaN means "Not a Number". It is the result of a number calculation that failed. By the old number rules, NaN is not equal to anything, not even itself.
  • Line 9 prints true. JavaScript has a negative zero, -0, and a positive zero, +0. Strict equality treats them as the same zero.

The playground below asks all four sets of rules about these cases, and about strings, numbers, BigInts, arrays, and objects too.

Same cart or look-alike cartPop out in the code editor (opens in a new tab)JavaScript
const firstCart = { total: 3 };const savedCart = firstCart;const otherCart = { total: 3 }; console.log(firstCart === savedCart);console.log(firstCart === otherCart);

Line 2 does not copy the cart. It copies the reference, so savedCart points to the same cart as firstCart. Line 5 prints true. Line 3 makes a brand-new cart with the same contents. Line 6 prints false, because it is a different object.

Two tags, firstCart and savedCart, point to the same cart, cart 1, which holds total: 3. A third tag, otherCart, points to a different cart, cart 2, which also holds total: 3. firstCart === savedCart is true. firstCart === otherCart is false.firstCartsavedCarttotal: 3cart 1same cart: trueotherCarttotal: 3cart 2other cart: false
Line 2 copies the reference, not the cart, so firstCart and savedCart point to one cart. otherCart points to a second cart that only looks the same. === asks: do both names point to the same cart?

=== never looks inside two objects to compare their properties. If you want to know whether two carts hold the same things, compare the properties yourself, like firstCart.total === otherCart.total.

Compare one pair four ways
The four checksPop out in the code editor (opens in a new tab)JavaScript
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));
NaNvs NaN
===false

=== has an old rule: NaN is not equal to anything, not even NaN.

==false

The types match, so == works just like ===.

Object.istrue

SameValue says NaN is the same as NaN.

SameValueZerotrue

SameValueZero says NaN matches NaN.

Try it yourself

You picked NaN vs NaN. Each answer below comes from running real JavaScript.

Pick a pair. See what strict equality, loose equality, Object.is, and SameValueZero each say about it.
Use === by default

Use === unless you can say exactly which conversion you want. With === the question stays simple: the type and the value match, or they don't.

Loose equality, step by step

STEP THROUGH

Loose equality, written ==, is not random. It follows a fixed list of steps. At each step it asks: "Are the two types the same yet? If not, is there a rule to convert one of them?"

To convert a value means to make a new value of a different type from it, like turning the text "5" into the number 5. The Type coercion lesson showed the spec's steps for this:

  • ToNumber turns a value into a number.
  • ToPrimitive turns an object into a primitive, like a string or a number.
  • ToBoolean turns a value into true or false.

== uses the first two.

Real-life analogyComparing prices in two money types

You want to know if a price in dollars and a price in rupees are the same. First you change the dollars into rupees. Then you compare. Loose equality does the same kind of work: it may convert values before it compares them.

In real life: Both prices are already in rupees
In JavaScript: Same type: compare with ===
In real life: Both price labels are missing
In JavaScript: null == undefined is true
In real life: Change one price into rupees first
In JavaScript: Convert a string, boolean, or object first
In real life: A price that can't be changed into rupees
In JavaScript: No rule fits, so the answer is false

Here are the steps of ==, in order:

  1. Same type? Compare with strict equality.
  2. One side is null and the other is undefined? The answer is true. Next to anything else, null and undefined give false.
  3. A number and a string? Turn the string into a number with ToNumber, then start again.
  4. A boolean? Turn it into a number: true becomes 1 and false becomes 0. Then start again.
  5. An object and a primitive? Turn the object into a primitive with ToPrimitive, then start again.
  6. A BigInt and a string or a number? Use the BigInt rules shown below.
  7. No rule fits? The answer is false.
Four loose checksPop out in the code editor (opens in a new tab)JavaScript
console.log(null == undefined);console.log(false == 0);console.log("" == 0);console.log("tea" == 0);
  • Line 1 prints true. That is the special null and undefined rule.
  • Line 2 prints true. false becomes 0, and 0 equals 0.
  • Line 3 prints true. The empty string "" becomes 0.
  • Line 4 prints false. The word "tea" can't become a real number, so it becomes NaN. And NaN is not equal to 0.

A BigInt is a whole number of any size. You write it with an n at the end, like 0n. Loose equality has its own rules for BigInts.

Compare a BigInt with a string and a numberPop out in the code editor (opens in a new tab)JavaScript
console.log(0n == "0");console.log(0n === 0);console.log(0n == 0);console.log(1n == 1.5);
  • Line 1 prints true. == reads the string "0" as the BigInt 0n.
  • Line 2 prints false. === never converts, and a BigInt and a number are different types.
  • Line 3 prints true. == compares a BigInt and a number by their value.
  • Line 4 prints false. 1.5 is not a whole number, so it can't match any BigInt.

The picture shows the steps for false == "". The step-through below starts with this same pair.

false == "" in three steps. First false becomes 0, giving 0 == "". Then "" becomes 0, giving 0 == 0. Now both sides are numbers, so the answer is true.false == ""false → 00 == """" → 00 == 0same type nowtrue
Each step changes one side, then starts again. First false becomes 0. Then the empty string "" becomes 0. Now both sides are numbers, so == compares them like === and gives true.
Step through ==
Step 0 of 4Ready
Your turn: follow the blue line

Walk through the steps of == for the pair you chose. The last result is checked against real JavaScript ==.

Running in
  1. script
Next: line 1
// 1. Same type? Compare with ===.// 2. null and undefined match only each other.// 3. Strings, booleans, and objects may be converted.// 4. BigInts have their own string and number rules.// 5. No rule fits? The answer is false.
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick a pair to run through the steps of ==

Picking a new pair starts the steps again from the top.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
The one == you will often see

value == null is a short way to write value === null || value === undefined. Many teams use it on purpose. It does not match false, 0, or the empty string "".

Object.is, NaN & -0

STEP THROUGH

Object.is(a, b) is a function that compares two values. It uses the rules the spec calls SameValue. It never converts values. For objects, it checks the reference, just like ===. It gives a different answer from === in only two cases:

  • Object.is(NaN, NaN) is true. === says false.
  • Object.is(-0, +0) is false. === says true.
Two pairs checked two ways. NaN and NaN: === says false, Object.is says true. -0 and +0: === says true, Object.is says false.===Object.isNaNNaNfalsetrue-0+0truefalse
=== and Object.is agree on almost every pair. These two pairs are the only ones where they give opposite answers.
Check a score that failed to convertPop out in the code editor (opens in a new tab)JavaScript
const score = Number("not a score"); console.log(Number.isNaN(score));console.log(Object.is(score, NaN));console.log(Object.is(-0, 0));
  • Line 1 tries to turn the text "not a score" into a number. That fails, so score is NaN.
  • Line 3 prints true. Number.isNaN is a function made for exactly this check.
  • Line 4 asks the same question with Object.is. It also prints true.
  • Line 5 prints false, because SameValue keeps negative zero apart from positive zero.
NaN and -0 under Object.is
Step 0 of 7Ready
Your turn: follow the blue line

Step through the two values that === treats in a surprising way: NaN and negative zero.

Running in
  1. script
Next: line 1
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);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Where does negative zero come from? Some calculations make it, such as Math.round(-0.4) or -1 * 0. Code that only shows numbers on a page can usually ignore it. But some number code can't, because 1 / -0 gives -Infinity while 1 / 0 gives Infinity. JSON loses the sign: JSON.stringify(-0) gives the text "0".

Checking for NaN

Use Number.isNaN(value). You may also see the old trick value !== value. It works because NaN is the only value that is not equal to itself. But it is hard to read, so prefer Number.isNaN.

SameValueZero in includes & Map

INTERACTIVE

SameValueZero is the set of rules that lists and collections use to find values. It takes one rule from each of the other two:

  • Like Object.is, it says NaN is the same as NaN.
  • Like ===, it says -0 and +0 are the same.

Three tools use it. includes checks whether an array holds a value. A Set is a collection that keeps each value only once. A Map stores values under keys. So [NaN].includes(NaN) is true, and new Set([-0]).has(0) is true. But [NaN].indexOf(NaN) is -1, because indexOf uses ===.

Search a list, then remove repeatsPop out in the code editor (opens in a new tab)JavaScript
const tasks = [NaN, "tea", NaN]; console.log(tasks.includes(NaN));console.log(tasks.indexOf(NaN)); const ids = new Set([1, 1, 2]);console.log(ids.size);
  • Line 3 prints true. includes finds NaN.
  • Line 4 prints -1, which means "not found". indexOf uses ===, and NaN === NaN is false.
  • Line 6 puts 1, 1, and 2 into a Set, which keeps each value once. Line 7 prints 2.
The array tasks holds NaN, "tea", and NaN. tasks.includes(NaN) finds the first NaN and gives true. tasks.indexOf(NaN) finds nothing and gives -1.tasksNaN"tea"NaNtasks.includes(NaN)truefound NaNtasks.indexOf(NaN)-1not found
includes uses SameValueZero, where NaN matches NaN, so it finds one. indexOf uses ===, where NaN never matches, so it gives -1: not found.
SameValueZero in includes, Map, and Set
Step 0 of 6Ready
Your turn: follow the blue line

See why includes, Map, and Set can find NaN and treat -0 and +0 as one value, while indexOf can't find NaN.

Running in
  1. script
Next: line 1
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));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Map or Set: what does it find?
SameValueZero checksPop out in the code editor (opens in a new tab)JavaScript
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));
MapSameValueZero

new Map([[NaN, "stored"]]).get(NaN) returns stored.

Try it yourself

Map keys use SameValueZero, so you can get a value stored under NaN by asking with another NaN.

Switch between the Map lines and the Set lines of the code above.

The table below asks all four sets of rules about four well-known pairs. Read each row as: "for this pair, each set of rules gives this answer."

Four sets of rules on four well-known pairs
Pair=====Object.isSameValueZero
NaN / NaNfalsefalsetruetrue
-0 / +0truetruefalsetrue
"1" / 1falsetruefalsefalse
null / undefinedfalsetruefalsefalse

Where you'll use this

Real code uses a few simple choices:

  • Use === for everyday comparisons.
  • Use value == null when you mean "the value is missing", whether it is null or undefined.
  • Use Object.is when your code must treat NaN as equal to NaN, or must see the sign of zero.

React, a popular library for building pages, uses Object.is to check whether some state has changed. So these details sometimes matter when you fix bugs in a page.

Three everyday choicesPop out in the code editor (opens in a new tab)JavaScript
if (user.middleName == null) {  showOptionalNameField();} if (Object.is(nextTemperature, previousTemperature)) {  skipChartUpdate();} const uniqueIds = new Set(idsFromSeveralLists);
  • Line 1 checks for a missing middle name: null or undefined.
  • Line 5 skips redrawing a chart when the temperature has not changed.
  • Line 9 makes a Set, which keeps each ID only once.
A profile, a form, and a list of IDsPop out in the code editor (opens in a new tab)JavaScript
const profile = { nickname: null };const ageText = "3";const ids = [1, 1, 2]; console.log(profile.nickname == null);console.log(Number(ageText) === 3);console.log(new Set(ids).size);
  • Line 5 checks a profile field that may be missing in either way. It prints true.
  • Line 6 turns the form text into a number first, then uses ===. It prints true.
  • Line 7 drops the repeated ID and prints 2.
Convert form text before you comparePop out in the code editor (opens in a new tab)JavaScript
const savedPin = 1234;const enteredText = "1234";const enteredPin = Number(enteredText); console.log(enteredPin === savedPin);

Line 2 is text, because form fields always give you strings. Line 3 turns it into a number on purpose. Now line 5 can use ===, and no hidden conversion can let a wrong value through. Real passwords are different: don't compare them in page code at all. Let the server check them.

A Set does some of this work for you. new Set(values) uses SameValueZero to remove repeats. So all the NaN values become one, and -0 and +0 count as one value.

Common misconceptions

“Strict equality works like equality in maths.”

It does for normal numbers. But NaN === NaN is false, and -0 === +0 is true. These follow JavaScript's number rules, not school maths.

“Loose equality is random.”

It follows fixed steps in a fixed order. But the steps are easy to misread. Follow them one by one, or use == only for clear patterns like value == null.

“Objects that look the same are equal.”

Two objects can have the same properties and still be two separate objects. === compares references. It never compares the properties inside.

“Object.is is just a stricter ===.”

It is stricter about zero: it says -0 and +0 are different. But it is less strict about NaN: it says NaN is the same as NaN. It is different, not just stricter.

“includes and indexOf search the same way.”

They differ for NaN. includes uses SameValueZero, so it finds NaN. indexOf uses ===, so it doesn't.

Which rules make it true?
  • Turns "1" into 1, so "1" and 1 match
  • Says null and undefined match
  • Says NaN matches NaN, but -0 does not match +0
  • [NaN].includes(NaN) is true
  • Never converts, and says -0 matches +0
  • new Set([-0]).has(0) is true
  • obj === obj is true
  • Object.is(-0, +0) is false
Try it yourself
0 of 8 correct

Each card is a true statement. Put it under the set of rules that it describes.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Practice: equality & sameness

5 EXERCISES
Exercise 1 · Warm-upPredict three NaN checks

Predict the three printed values, in order. Each one is true or false.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log([NaN].includes(NaN));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWalk through "0" == false

    Write the printed value. Then say each step to yourself: what changes, and into what?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    console.log("0" == false);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix a NaN check

      Change the function body so it returns true for a real NaN, and false for the string "NaN".

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function isActuallyNaN(value) {
        return value === NaN;
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeRemove repeats, including NaN

        Predict the first printed value. Then explain why the list of unique values still has NaN in it.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const values = [NaN, NaN, -0, 0, "0", "0"];
        const unique = [...new Set(values)];
        console.log(unique.length);
        console.log(unique.includes(NaN));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain the sign of zero

          In one sentence, explain why Object.is(-0, 0) is false even though -0 === 0 is true.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          console.log(Object.is(-0, 0));
          console.log(1 / -0);

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            8 QUESTIONS

            For each question, first name the set of rules. Then follow its steps. Every answer comes with an explanation.

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Which check should you use by default in everyday code?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does "1" == 1 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("1" == 1);

              Choose an answer to see the explanation.

            3. Question 3 of 8What does NaN === NaN print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(NaN === NaN);

              Choose an answer to see the explanation.

            4. Question 4 of 8What does Object.is(-0, 0) print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(Object.is(-0, 0));

              Choose an answer to see the explanation.

            5. Question 5 of 8Which one uses SameValueZero?

              Choose an answer to see the explanation.

            6. Question 6 of 8What does null == undefined print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(null == undefined);

              Choose an answer to see the explanation.

            7. Question 7 of 8What does [NaN].indexOf(NaN) print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log([NaN].indexOf(NaN));

              Choose an answer to see the explanation.

            8. Question 8 of 8Which line is a good, on-purpose use of loose equality?

              Choose an answer to see the explanation.

            Key takeaways

            • Use === by default. It checks for the same type and the same value, and never converts.
            • == follows fixed conversion steps in order. Use it on purpose, mostly as value == null.
            • Object.is uses SameValue. NaN matches NaN, and -0 is different from +0.
            • includes, Map, and Set use SameValueZero. NaN matches NaN, and -0 matches +0.
            • When both sides are objects, all four compare references. None of them compares the properties inside.

            In one line:
            JavaScript has four ways to check sameness: === never converts, == may convert first, Object.is notices NaN and the sign of zero, and SameValueZero is what lists and collections use.

            Up next: Checking types reliably.

            CompleteFrontend Clear concepts. Working examples.