Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~24 min· topic 6 of 6

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 treats NaN as equal to NaN and 0 as 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", 42 or [].
Falsy
One of the eight values that count as false: false, 0, -0, 0n, "", null, undefined, NaN.
Condition
The value inside if ( ) or while ( ) 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.

app.jswhole filejavascript
5 === 5        // true
5 === "5"      // false: number vs string
"a" !== "b"    // true

02See what == does

== converts first. The path it takes depends on both types, which is why its results are hard to predict.

See what == doesdiagram
Rendering diagram…

03Compare objects by identity

Two separately created objects live in two places in memory, so they are different, whatever they contain.

app.jswhole filejavascript
const a = { n: 1 };
const b = { n: 1 };
const c = a;
a === b // false: two objects
a === c // true: same object

04Handle 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.

app.jswhole filejavascript
const name = "";
if (name) {
  console.log("Hello " + name);
} else {
  console.log("No name given"); // runs: "" is falsy
}

Try it yourself

  1. 1

    Predict loose equality

    Before running the first example, add console.log("1" == 1, "1" == true, "2" == true);. Hint: true becomes 1. Predict all three, then run.

  2. 2

    Fill the name

    In the last example, change const name = ""; to const name = " "; (one space). Which branch runs now, and why?

  3. 3

    Copy, not share

    In the third example, change const c = a; to const c = { n: a.n };. Predict both the comparison and the last line.

Code & diagrams

=== vs == New tab

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 true
NaN, -0 and Object.is ES2015+ New tab

Console

(nothing was printed)

Expected console output

false true
true false
true false
true -1
Objects are equal only to themselves New tab

`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
99
Truthy and falsy values New tab

The first two lines are the eight falsy values. Everything on the third line is truthy.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

false false false false
false false false false
true true true true true true
No name given

Break 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"); }

terminal
$ Console
── what you'll see ──
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.

terminal
$ the page shows
── what you'll see ──
(the code inside the if never runs)

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 but 0 equals -0), which includes, Map, Set keys use. That's why new Set([NaN, NaN]).size is 1.

  • ▸

    document.all is the only falsy object, and document.all == null is true: 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 or e plus a combining accent. Normalise with str.normalize() before comparing user input (Topic 4.2).

  • ▸

    ESLint's eqeqeq rule (with the "null": "ignore" option) is the standard way teams enforce === while allowing == null.

Remember this

  1. 1

    === is strict equality: it is true only when both values have the same type and the same value. No conversion happens: 5 === 5 is true, 5 === "5" is false. !== is its opposite ("not strictly equal"). This is the comparison to use almost everywhere.

  2. 2

    == is loose equality: if the types differ, it first converts them using coercion rules (Topic 1.5), then compares. So 5 == "5" is true, 0 == "" is true, false == "0" is true and [] == false is true. The rules are fixed (the spec's IsLooselyEqual algorithm) but hard to remember, and they aren't even consistent: 0 == "" and 0 == "0" are both true, yet "" == "0" is false. != is the loose "not equal".

  3. 3

    There is one useful case for ==: x == null is true for both null and undefined and nothing else, so it checks "is this missing?" in one go. null is not loosely equal to 0, "" or false. But beware relational operators: null >= 0 is true because >= converts null to 0, while == has its own special rule for null. Many teams allow == null and nothing else.

  4. 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 } is false and [1, 2] === [1, 2] is false. Only two references to the same object are equal. Primitives, by contrast, are compared by value: "hi" === "hi" is true. To compare contents you check the parts yourself (the arrays and objects phases show how).

  5. 5

    === has two odd results that come from IEEE 754 numbers (Topic 1.3): NaN === NaN is false, and 0 === -0 is true. Object.is(a, b) (ES2015) is "same value" equality: it behaves like === except Object.is(NaN, NaN) is true and Object.is(0, -0) is false. React uses Object.is to decide whether state changed. Array methods differ too: [NaN].includes(NaN) is true but [NaN].indexOf(NaN) is -1.

  6. 6

    Every if (...), while (...), !, && and || turns its value into a boolean. A value that becomes false is falsy; there are exactly eight: false, 0, -0, 0n, "", null, undefined and NaN. Every other value is truthy, including "0", "false", " " (a space), [], {} and negative numbers. So if (items.length) means "if there are any items", and if (name) means "if name is a non-empty string". Be careful when 0 or "" is a valid value: if (count) treats a real count of 0 as "missing"; write count !== undefined or use ?? (Topic 2.2) instead.

Explain it without notes

01

What is the difference between == and ===? Which do you use and why?

02

Why is [] == false true, while [] is truthy?

03

What does Object.is do differently from ===?

04

List the falsy values in JavaScript. What's a common bug caused by truthiness checks?

Practice

01

Log whether the string "10" and the number 10 are strictly equal, then convert the string so that the strict comparison is true.

02

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.

03

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 when 0 or "" 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 == null is the one useful idiom.

  • I know objects compare by reference.

  • I can use Object.is and Number.isNaN for NaN and -0.

  • I can list the eight falsy values and avoid treating 0 as missing.