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 betruebecause the contents “look the same” - Beginners who already met
==vs===but now see objects and arrays everywhere - Anyone debugging
"10" + 2in 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
- Log
{} === {}anda === afor the sameconst a = {}. - Reproduce
"5" + 1and"5" - 1. - Compare
0 == falsevs0 === false. - 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.
- MERN MongoDB modeling — see objects turn into real documents and nested structures
- JSON Formatter — inspect deep objects before you mutate or copy them