Lesson 7·M1 Core Mechanics
Coercion, Truthy/Falsy & Equality
OpenLearning Objectives
By the end of this lesson you should be able to:
- List the falsy values from memory
- Explain what
==does that===doesn't - Explain
0 == '',null == undefinedandNaN !== NaN - Say why
+behaves differently from every other operator - Choose between
||and??correctly
What Coercion Is
Coercion is JavaScript converting a value from one type to another. It happens implicitly when an operator needs a different type, and explicitly when you ask for it.
console.log('5' * 2); // implicit → number
console.log(Number('5') * 2); // explicit → same result, clearer intent
Output:
10
10
The rules aren't random, but they are surprising often enough that the profession settled on
"be explicit, and use ===".
The Falsy Values
Exactly eight values are falsy. Everything else — including '0', 'false', [] and
{} — is truthy.
false
0
-0
0n (BigInt zero)
"" (empty string)
null
undefined
NaN
console.log(Boolean([]), Boolean({}), Boolean('0'), Boolean(' '));
Output:
true true true true
Empty array, empty object, the string '0' and a single space are all truthy. That trips
people constantly, especially with if (arr) when they meant if (arr.length).
== vs ===
=== compares type and value with no conversion. == converts first, then compares.
console.log(1 === '1');
console.log(1 == '1');
Output:
false
true
The conversion rules that actually matter:
| Comparison | Result | Why |
|---|---|---|
1 == '1' | true | String converts to number |
0 == '' | true | Both convert to 0 |
0 == '0' | true | '0' converts to 0 |
'' == '0' | false | Both are strings — no conversion, different text |
null == undefined | true | Special-cased in the spec |
null == 0 | false | null only ever equals undefined |
NaN == NaN | false | NaN is equal to nothing, including itself |
[] == false | true | [] → '' → 0, and false → 0 |
[1] == 1 | true | [1] → '1' → 1 |
Note
The one place == is genuinely useful is x == null, which is true for exactly null
and undefined. Some style guides allow that single exception; everything else should be
===.
NaN Is Its Own Problem
console.log(NaN === NaN);
console.log(Number.isNaN(NaN));
console.log(Object.is(NaN, NaN));
Output:
false
true
true
Use Number.isNaN(x) — not the global isNaN(x), which coerces first and reports
isNaN('hello') as true.
+ Is the Odd One Out
+ means addition or string concatenation. If either side is a string, it concatenates.
Every other arithmetic operator converts to number.
console.log(1 + '2');
console.log(1 - '2');
console.log('3' * '4');
console.log([] + {});
Output:
12
-1
12
[object Object]
This is the source of most "why is my total '105'?" bugs — one value arrived from an input
or an API as a string.
How Objects Convert
When an object meets a primitive operator, the engine asks it for a primitive: valueOf()
first for numeric contexts, then toString().
console.log([] + '');
console.log([1, 2] + '');
console.log({} + '');
console.log(new Date(0).getTime());
Output:
1,2
[object Object]
0
An empty array stringifies to '' — which is why [] == false is true, and why
[] == ![] is also true. You can control this yourself:
const money = {
amount: 50,
valueOf() { return this.amount; },
toString() { return `$${this.amount}`; },
};
console.log(money + 10);
console.log(`${money}`);
Output:
60
$50Converting on Purpose
console.log(Number('42'), Number(''), Number('12px'));
console.log(parseInt('12px', 10), parseFloat('3.5rem'));
console.log(String(42), (42).toString());
console.log(Boolean(''), !!'text');
Output:
42 0 NaN
12 3.5
42 42
false true
Number() is all-or-nothing; parseInt reads as far as it can and stops. Number('') being
0 rather than NaN is the quirk behind 0 == ''.
|| vs ??
|| falls back on any falsy value. ?? falls back only on null or undefined.
const count = 0;
const name = '';
console.log(count || 10);
console.log(count ?? 10);
console.log(name || 'Anonymous');
console.log(name ?? 'Anonymous');
Output:
10
0
Anonymous
If 0 or '' are legitimate values — a quantity, a cleared text field — || silently
throws them away. That's a real bug, and ?? is the fix.
Common Interview Questions
Q1. What's the difference between == and ===?
===compares type and value with no conversion.==coerces the operands to a common type first, which is why1 == '1'is true.I use
===everywhere, with the one arguable exception ofx == nullwhen I want to catch bothnullandundefined.
Q2. List the falsy values.
false,0,-0,0n, the empty string,null,undefinedandNaN— eight of them. Everything else is truthy, including empty arrays and objects, and the string'0'.
Q3. Why is 0 == '' true?
Because
==converts both sides to numbers when the types differ, andNumber('')is0. Comparing'' == '0'is false, though — those are both strings, so no conversion happens and the text simply differs.
Q4. Why is NaN !== NaN?
Because the spec defines
NaNas not equal to anything, including itself — it represents "not a valid number", and two invalid results aren't meaningfully the same.To test for it, use
Number.isNaNorObject.is. The globalisNaNcoerces first, so it returns true for'hello'too.
Q5. What's the difference between || and ???
||falls back on any falsy value;??only onnullorundefined. So with a count of0,count || 10gives10whilecount ?? 10correctly gives0.I default to
??for defaults, because0and''are usually real values.
Q6. Why does 1 + '2' give '12' but 1 - '2' give -1?
+is overloaded: if either operand is a string it concatenates. Every other arithmetic operator has only the numeric meaning, so it coerces the string to a number.
Senior follow-up: How does an object become a primitive in a comparison?
The engine calls the object's
Symbol.toPrimitiveif present, otherwisevalueOf()and thentoString(), taking the first primitive it gets — with the order depending on whether a number or a string is preferred in that context.That's the machinery behind
[] == false: the array becomes'', which becomes0, andfalseis also0. You can hook into it deliberately by definingvalueOfandtoStringon your own objects.
Best Practices
✅ Use === and !== by default
✅ Use ?? for defaults, || only when every falsy value really should fall back
✅ Convert explicitly at the boundary — Number(input.value) as soon as it arrives
✅ Check arr.length rather than the array itself for emptiness
❌ Don't rely on == coercion to "do the right thing"
❌ Don't use the global isNaN — use Number.isNaN
Coding Exercise
Predict every line, then run it.
console.log(1 == '1');
console.log(1 === '1');
console.log(0 == '');
console.log('' == '0');
console.log(null == undefined);
console.log(null === undefined);
console.log(NaN == NaN);
console.log([] == false);
console.log(Boolean([]));
console.log(0 || 'fallback');
console.log(0 ?? 'fallback');
Answers
true
false
true
false
true
false
false
true
true
fallback
0
The two worth being able to explain out loud are 0 == '' (both become 0) and
[] == false alongside Boolean([]) === true — an array is truthy, but it coerces to 0
in a loose comparison. Those aren't contradictory; they're different operations.
Mini Challenge
Write toNumber(input) that converts a form value to a number and returns null for
anything that isn't a valid one — handling '', ' ', '12px', '3.5', null and
undefined.
Then explain why you couldn't just use Number() on its own.
Lesson Summary
Recap
- Eight falsy values; empty arrays and objects are truthy
===compares type and value;==converts first0 == ''is true because both become0;'' == '0'is false because both are stringsnullloosely equals onlyundefined;NaNequals nothing at all+concatenates if either side is a string — every other operator goes numeric- Objects convert via
Symbol.toPrimitive,valueOfthentoString ??falls back only onnull/undefined;||falls back on any falsy value
Check your understanding
Answer each one out loud. Then rate yourself honestly.
0/6List the eight falsy values.
Explain 0 == '' and '' == '0' in the same breath.
Why is NaN !== NaN, and how do you test for it properly?
Why does [] == false hold while if ([]) runs the block?
When would || produce a bug that ?? wouldn't?
What sequence does an object go through to become a primitive?
What's Next
Lesson 8 — Objects. Everything in JavaScript that isn't a primitive is one, so this is
the foundation for prototypes, this, and every framework you'll touch.