What you'll be able to do

By the end of this page you should be able to explain why two “empty” objects are not equal with ===, why "5" + 1 becomes "51" but "5" - 1 becomes 4, and when to stick with === after the references chapter.

Previous post: shallow vs deep copy.

Who this is for

  • People who expect {} === {} to be true because the contents “look the same”
  • Beginners who already met == vs === but now see objects and arrays everywhere
  • Anyone debugging "10" + 2 in the console

You can skip this if identity vs value and string + vs math - already feel predictable. Come back when an interview question or a log line still feels unfair.

Identity: two literals, two references

console.log({} === {}); // false
console.log([] === []); // false

Scenario A — two separate objects in memory. Each {} creates a new reference. === compares identity, not “same shape.”

Scenario B — same variable twice. const a = {}; const b = a; a === b is true — one object, two names. This connects directly to the references chapter.

Comparing “same data” in two objects is a different job (field-by-field checks, or helpers later). === alone is not that job.

"5" + 1 vs "5" - 1

console.log("5" + 1); // "51" — string glue
console.log("5" - 1); // 4 — minus tries math

Scenario A — building a label. "Score: " + points is string concatenation. If points is a number, it becomes text first.

Scenario B — subtract forces numbers. - is not string-friendly; "5" becomes 5. Same string, different operator, different rule.

You already saw + glue in the strings chapter. After references, the surprise is often “why did math break?” — because a string was still in the mix.

== still coerces (quick reminder)

console.log(0 == false); // true
console.log(0 === false); // false

Scenario A — legacy code or quick snippets. == converts types before comparing.

Scenario B — daily habit. Prefer === unless you have a documented reason for ==. Fewer silent branches.

null and typeof (one classic)

console.log(typeof null); // "object" (historical quirk)
console.log(null === null); // true

Scenario A — checking for missing. Use value === null or value == null (only null/undefined) when you mean that — not typeof.

Scenario B — do not over-index on the quirk. Know it exists; do not build logic on typeof null === "object".

A tiny practice file

Save as coercion.html.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>coercion</title>
  </head>
  <body>
    <button id="go">Run surprises</button>
    <pre id="out"></pre>
    <script>
      document.getElementById("go").addEventListener("click", function () {
        const a = {};
        const b = {};
        const shared = a;
        document.getElementById("out").textContent =
          "{} === {} → " + (a === b) + "\n" +
          "a === shared → " + (a === shared) + "\n" +
          '"5" + 1 → ' + ("5" + 1) + "\n" +
          '"5" - 1 → ' + ("5" - 1);
      });
    </script>
  </body>
</html>

Mistakes I see a lot

1. Using === to compare object contents. Compare fields or use a dedicated approach.

2. Adding numbers to strings without converting on purpose. Number(x) or template literals with clear intent.

3. Assuming [] is falsy. Empty array is truthy in if ([]).

4. Mixing == and === randomly in one file.

5. Treating coercion surprises as “JavaScript is broken.” They are rules — annoying sometimes, but learnable.

6. Deep equality tests with JSON.stringify without stable key order. Fine for quick logs; fragile for real equality.

What to try before the next post

  1. Log {} === {} and a === a for the same const a = {}.
  2. Reproduce "5" + 1 and "5" - 1.
  3. Compare 0 == false vs 0 === false.
  4. Build the tiny HTML file.

Next in this series: JSON parse and stringify — turning objects into text for APIs and storage, and catching parse errors.

Try this next outside the series

Objects and references click faster when you connect them to state, payloads, and database-shaped data.