Topic 1.6
Equality and Truthiness
In one line
Use === to compare values without hidden conversion, Object.is for the two cases === gets wrong (NaN and -0), and learn the eight falsy values, because every if converts its condition to true or false.
Think of it like this
Checking a ticket at a concert. A strict guard checks that the ticket is exactly right: correct name, correct date. A relaxed guard lets you in if the ticket is "close enough" after squinting at it: a photo of the ticket, a printed copy. The strict guard is ===, the relaxed guard is ==. The relaxed one is easier to get past by accident.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Strict equality (===)
- True only when both values have the same type and the same value. No conversion.
- Loose equality (==)
- Converts the values to a common type first, then compares. Easy to surprise you.
- Object.is
- A comparison like
===, but it treatsNaNas equal toNaNand0as different from-0. - Reference
- The identity of an object. Two objects are equal only if they are the very same object.
- Truthy
- A value that counts as true in an
if, like"hi",42or[]. - Falsy
- One of the eight values that count as false:
false,0,-0,0n,"",null,undefined,NaN. - Condition
- The value inside
if ( )orwhile ( )that decides what runs. - !==
- "Not strictly equal": the opposite of
===.
Step by step
01Compare strictly
=== first checks the types. Different types means false immediately; the same type means compare the values.
5 === 5 // true
5 === "5" // false: number vs string
"a" !== "b" // true02See what == does
== converts first. The path it takes depends on both types, which is why its results are hard to predict.
03Compare objects by identity
Two separately created objects live in two places in memory, so they are different, whatever they contain.
const a = { n: 1 };
const b = { n: 1 };
const c = a;
a === b // false: two objects
a === c // true: same object04Handle NaN and -0
Use Number.isNaN(x) or Object.is(x, NaN) for NaN. Use Object.is(x, -0) in the rare case where negative zero matters.
05Let if convert to boolean
The condition of an if becomes true or false using the truthy/falsy rules. if and else are taught properly in Topic 2.3.
const name = "";
if (name) {
console.log("Hello " + name);
} else {
console.log("No name given"); // runs: "" is falsy
}Try it yourself
- 1
Predict loose equality
Before running the first example, add
console.log("1" == 1, "1" == true, "2" == true);. Hint:truebecomes1. Predict all three, then run. - 2
Fill the name
In the last example, change
const name = "";toconst name = " ";(one space). Which branch runs now, and why? - 3
Copy, not share
In the third example, change
const c = a;toconst c = { n: a.n };. Predict both the comparison and the last line.
Code & diagrams
Line three shows `==` isn't even transitive: a == b and a == c, but b != c.
Console
(nothing was printed)Expected console output
true false
true
true true false
true true
true false
false trueConsole
(nothing was printed)Expected console output
false true
true false
true false
true -1`c` is not a copy: it's a second name for the object `a` points to.
Console
(nothing was printed)Expected console output
false true
false
true
99The first two lines are the eight falsy values. Everything on the third line is truthy.
Preview
Console
Nothing logged yet.Expected console output
false false false false
false false false false
true true true true true true
No name givenBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Treat 0 as missing
Write const stock = 0; if (!stock) { console.log("Stock not loaded yet"); }
Break #2
Compare an input with a number using ===
Write if (ageInput.value === 18) { ... } for an input where the user typed 18.
Myth vs fact
Myth
== checks value and === checks value and type, so == is fine if you're careful.
Fact
== runs a whole conversion algorithm with surprising results ([] == false, "" == 0). Use === and convert explicitly; allow == null only as a deliberate idiom.
Myth
An empty array or object is falsy.
Fact
All objects are truthy, even empty ones. Test arr.length === 0 or Object.keys(obj).length === 0.
Myth
Two objects with the same contents are equal.
Fact
Objects compare by reference. { a: 1 } === { a: 1 } is false; only the same object equals itself.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The spec has four equality algorithms: IsStrictlyEqual (
===), IsLooselyEqual (==), SameValue (Object.is) and SameValueZero (like SameValue but0equals-0), whichincludes,Map,Setkeys use. That's whynew Set([NaN, NaN]).sizeis 1. - ▸
document.allis the only falsy object, anddocument.all == nullistrue: a deliberate legacy exception so old "is this Internet Explorer?" checks keep working. - ▸
Comparing strings with
===is by UTF-16 code units, so visually identical text can differ:"é"can be one code point oreplus a combining accent. Normalise withstr.normalize()before comparing user input (Topic 4.2). - ▸
ESLint's
eqeqeqrule (with the"null": "ignore"option) is the standard way teams enforce===while allowing== null.
Remember this
- 1
===is strict equality: it istrueonly when both values have the same type and the same value. No conversion happens:5 === 5istrue,5 === "5"isfalse.!==is its opposite ("not strictly equal"). This is the comparison to use almost everywhere. - 2
==is loose equality: if the types differ, it first converts them using coercion rules (Topic 1.5), then compares. So5 == "5"istrue,0 == ""istrue,false == "0"istrueand[] == falseistrue. The rules are fixed (the spec's IsLooselyEqual algorithm) but hard to remember, and they aren't even consistent:0 == ""and0 == "0"are both true, yet"" == "0"is false.!=is the loose "not equal". - 3
There is one useful case for
==:x == nullistruefor bothnullandundefinedand nothing else, so it checks "is this missing?" in one go.nullis not loosely equal to0,""orfalse. But beware relational operators:null >= 0istruebecause>=convertsnullto0, while==has its own special rule fornull. Many teams allow== nulland nothing else. - 4
Objects (including arrays and functions) are compared by reference, not by contents: two different objects are never equal, even if they look identical.
{ n: 1 } === { n: 1 }isfalseand[1, 2] === [1, 2]isfalse. Only two references to the same object are equal. Primitives, by contrast, are compared by value:"hi" === "hi"istrue. To compare contents you check the parts yourself (the arrays and objects phases show how). - 5
===has two odd results that come from IEEE 754 numbers (Topic 1.3):NaN === NaNisfalse, and0 === -0istrue.Object.is(a, b)(ES2015) is "same value" equality: it behaves like===exceptObject.is(NaN, NaN)istrueandObject.is(0, -0)isfalse. React usesObject.isto decide whether state changed. Array methods differ too:[NaN].includes(NaN)istruebut[NaN].indexOf(NaN)is-1. - 6
Every
if (...),while (...),!,&&and||turns its value into a boolean. A value that becomesfalseis falsy; there are exactly eight:false,0,-0,0n,"",null,undefinedandNaN. Every other value is truthy, including"0","false"," "(a space),[],{}and negative numbers. Soif (items.length)means "if there are any items", andif (name)means "if name is a non-empty string". Be careful when0or""is a valid value:if (count)treats a real count of 0 as "missing"; writecount !== undefinedor use??(Topic 2.2) instead.
Explain it without notes
What is the difference between == and ===? Which do you use and why?
Why is [] == false true, while [] is truthy?
What does Object.is do differently from ===?
List the falsy values in JavaScript. What's a common bug caused by truthiness checks?
Practice
Log whether the string "10" and the number 10 are strictly equal, then convert the string so that the strict comparison is true.
Write a check that logs missing when a variable discount is null or undefined, but not when it is 0. Test it with all three values.
Log the result of Boolean for these values and explain any surprise: "0", [], NaN, " ".
Trade-offs
- ↔
===everywhere is predictable but means converting input yourself;==saves a conversion but makes readers run the coercion algorithm in their heads. - ↔
Truthiness checks (
if (items.length)) are short and idiomatic, but explicit checks (items.length > 0,x !== undefined) are clearer when0or""are valid values.
Done when you can
I use
===and!==by default and can explain why.I can predict a few classic
==results and know== nullis the one useful idiom.I know objects compare by reference.
I can use
Object.isandNumber.isNaNfor NaN and -0.I can list the eight falsy values and avoid treating
0as missing.