Episode 1 — Fundamentals / 1.18 — Operators and Type System
1.18.f — Truthy and Falsy Values
In one sentence: JavaScript automatically converts values to booleans in conditional contexts — exactly 8 values are falsy (convert to
false), and everything else is truthy (convert totrue), which creates both powerful patterns and treacherous bugs when0,"", or[]behave in unexpected ways.
Navigation: ← 1.18.e — typeof Operator · 1.18.g — Type Coercion →
1. The 8 falsy values (memorize this list)
| Value | Type | Boolean() result |
|---|---|---|
false | Boolean | false |
0 | Number | false |
-0 | Number | false |
0n | BigInt | false |
"" (empty string) | String | false |
null | Null | false |
undefined | Undefined | false |
NaN | Number | false |
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean(0n)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
There are exactly 8 falsy values. Everything else in JavaScript is truthy.
2. Everything else is truthy
This includes many values that surprise beginners:
// Obvious truthy values
console.log(Boolean(true)); // true
console.log(Boolean(1)); // true
console.log(Boolean("hello")); // true
console.log(Boolean(42)); // true
// SURPRISING truthy values
console.log(Boolean([])); // true (empty array!)
console.log(Boolean({})); // true (empty object!)
console.log(Boolean("0")); // true (string "0" is NOT 0!)
console.log(Boolean("false")); // true (string "false" is NOT false!)
console.log(Boolean(" ")); // true (space is a character!)
console.log(Boolean(new Boolean(false))); // true (object wrapper!)
console.log(Boolean(-1)); // true (any non-zero number)
console.log(Boolean(Infinity)); // true
console.log(Boolean(-Infinity)); // true
console.log(Boolean(new Date())); // true (any object)
console.log(Boolean(/regex/)); // true (RegExp is an object)
console.log(Boolean(function(){})); // true
Quick reference: truthy surprises
| Value | Truthy? | Why it surprises |
|---|---|---|
[] (empty array) | Yes | People expect "empty" to be falsy |
{} (empty object) | Yes | Same as above |
"0" | Yes | It is a non-empty string |
"false" | Yes | It is a non-empty string |
" " (whitespace) | Yes | It is a non-empty string |
new Boolean(false) | Yes | It is an object (objects are always truthy) |
new Number(0) | Yes | Same — object wrapper |
new String("") | Yes | Same — object wrapper |
-1 | Yes | Any number except 0, -0, NaN |
Infinity | Yes | It is a number, not 0/NaN |
3. Why [] is truthy but [] == false is true
This is one of the most confusing behaviors in JavaScript:
// [] is truthy
if ([]) {
console.log("This runs!"); // runs — [] is truthy
}
// But [] == false is true (!)
console.log([] == false); // true
Step-by-step explanation of [] == false
The abstract equality algorithm (==) applies these steps:
- Boolean vs anything: Convert
falseto0→ now comparing[] == 0 - Object vs number: Convert
[]to primitive →[].toString()→""→ now comparing"" == 0 - String vs number: Convert
""to0→ now comparing0 == 0 - Same type:
0 === 0→true
// The chain of conversions
console.log([].toString()); // ""
console.log(Number("")); // 0
console.log(Number(false)); // 0
// So: [] → "" → 0 == false → 0 → 0 ... 0 == 0 → true
More coercion traps
console.log([] == false); // true
console.log([] == 0); // true
console.log([] == ""); // true
console.log([0] == false); // true ([0].toString() = "0" → 0)
console.log([""] == false); // true ([""].toString() = "" → 0)
console.log([[]] == false); // true ([[]].toString() = "" → 0)
// But with ===, none of these are true
console.log([] === false); // false
console.log([] === 0); // false
console.log([] === ""); // false
Lesson: Use === to avoid these traps entirely.
4. Boolean conversion methods
Boolean() constructor (without new)
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean("hello")); // true
console.log(Boolean([])); // true
Double negation !!
console.log(!!0); // false
console.log(!!""); // false
console.log(!!"hello"); // true
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!{}); // true
console.log(!![]); // true
Which to use?
| Method | Pros | Cons |
|---|---|---|
Boolean(x) | Explicit, readable | Slightly longer |
!!x | Short, common idiom | Less obvious to beginners |
Most style guides accept both. Boolean() is clearer; !! is more common in the wild.
5. Truthy/falsy in control flow
if statements
const name = "";
if (name) {
console.log("Has name");
} else {
console.log("No name"); // This runs (empty string is falsy)
}
const items = [];
if (items.length) {
console.log("Has items");
} else {
console.log("No items"); // This runs (0 is falsy)
}
while loops
let input = "hello";
while (input) {
console.log(input);
input = ""; // Loop ends because "" is falsy
}
&& and ||
const user = { name: "Alice" };
// && returns first falsy or last truthy
const greeting = user.name && `Hello, ${user.name}!`;
console.log(greeting); // "Hello, Alice!"
// || returns first truthy or last falsy
const display = user.nickname || user.name || "Anonymous";
console.log(display); // "Alice"
Ternary operator
const count = 0;
const message = count ? `${count} items` : "No items";
console.log(message); // "No items" (0 is falsy)
// But what if count IS 0 and that's valid?
const message2 = count !== undefined ? `${count} items` : "No items";
console.log(message2); // "0 items" (explicit check)
6. Common bugs from falsy values
Bug 1: Valid 0 treated as missing
function displayScore(score) {
// BUG: score of 0 is treated as "no score"
if (!score) {
return "No score available";
}
return `Score: ${score}`;
}
console.log(displayScore(85)); // "Score: 85"
console.log(displayScore(0)); // "No score available" (WRONG!)
console.log(displayScore(null)); // "No score available" (correct)
// FIX: be explicit about what "missing" means
function displayScoreFixed(score) {
if (score === null || score === undefined) {
return "No score available";
}
// Or: if (score == null) — the one acceptable == null check
return `Score: ${score}`;
}
console.log(displayScoreFixed(0)); // "Score: 0" (correct!)
console.log(displayScoreFixed(null)); // "No score available"
Bug 2: Valid empty string treated as missing
function setUsername(input) {
// BUG: user intentionally clears their display name to ""
const name = input || "Anonymous";
return name;
}
console.log(setUsername("Alice")); // "Alice"
console.log(setUsername("")); // "Anonymous" (WRONG — user wanted empty!)
console.log(setUsername(null)); // "Anonymous" (correct)
// FIX: use ?? to only fall back for null/undefined
function setUsernameFixed(input) {
const name = input ?? "Anonymous";
return name;
}
console.log(setUsernameFixed("")); // "" (correct — user's choice)
console.log(setUsernameFixed(null)); // "Anonymous" (correct)
Bug 3: false as a legitimate value
function getSetting(key, settings) {
// BUG: false is a valid setting value
const value = settings[key] || "default";
return value;
}
const config = { darkMode: false, volume: 0, name: "" };
console.log(getSetting("darkMode", config)); // "default" (WRONG!)
console.log(getSetting("volume", config)); // "default" (WRONG!)
console.log(getSetting("name", config)); // "default" (WRONG!)
// FIX: use ??
function getSettingFixed(key, settings) {
return settings[key] ?? "default";
}
console.log(getSettingFixed("darkMode", config)); // false (correct)
console.log(getSettingFixed("volume", config)); // 0 (correct)
console.log(getSettingFixed("name", config)); // "" (correct)
Bug 4: Array/object emptiness check
const items = [];
// WRONG: [] is truthy, so this always passes
if (items) {
console.log("Has items"); // This ALWAYS runs for any array!
}
// CORRECT: check .length
if (items.length > 0) {
console.log("Has items");
}
// For objects: check Object.keys
const obj = {};
if (Object.keys(obj).length > 0) {
console.log("Has properties");
}
7. Real-world conditional traps with examples
API response handling
// API returns: { data: [], error: null, count: 0 }
const response = { data: [], error: null, count: 0 };
// TRAP: all these checks are wrong
if (!response.data) { /* won't run — [] is truthy */ }
if (!response.count) { /* runs — 0 is falsy, but count IS 0 */ }
if (!response.error) { /* runs — null is falsy (correct here) */ }
// BETTER
if (response.data.length === 0) { console.log("No data"); }
if (response.count === 0) { console.log("Zero count"); }
if (response.error === null) { console.log("No error"); }
Form validation
function validateForm(fields) {
const errors = [];
// TRAP: what if allowedAge is 0?
if (!fields.age) {
errors.push("Age is required"); // Bug if age is 0
}
// BETTER
if (fields.age === undefined || fields.age === null) {
errors.push("Age is required");
}
// Or with == null
if (fields.age == null) {
errors.push("Age is required");
}
return errors;
}
Environment variable parsing
// process.env values are always strings or undefined
const port = process.env.PORT || 3000;
// If PORT="0", this gives 3000 (but "0" is truthy, so this actually works!)
// If PORT="" (empty string), this gives 3000 (intended)
const debug = process.env.DEBUG || false;
// If DEBUG="false", debug becomes "false" (truthy string — bug!)
// BETTER: explicit parsing
const debugMode = process.env.DEBUG === "true";
const portNum = Number(process.env.PORT) || 3000;
8. Best practices: explicit comparisons vs truthy checks
When truthy checks are FINE
// Checking for null/undefined/missing when 0, "", false are not valid
if (user.name) { ... } // name must be a non-empty string
if (callback) { callback(); } // function or nothing
if (error) { handleError(error); } // error object or null
When you MUST be explicit
// When 0 is a valid value
if (count !== undefined) { ... } // not: if (count)
if (count != null) { ... } // null or undefined check
// When "" is a valid value
if (typeof name === "string") { ... } // not: if (name)
// When false is a valid value
if (setting !== undefined) { ... } // not: if (setting)
// When checking array emptiness
if (arr.length > 0) { ... } // not: if (arr)
// When checking object emptiness
if (Object.keys(obj).length > 0) { ... } // not: if (obj)
Decision guide
| Scenario | Use truthy check? | Preferred approach |
|---|---|---|
| String must be non-empty | Yes | if (str) |
| String can be empty (valid) | No | if (str != null) or if (typeof str === "string") |
| Number must be non-zero | Yes | if (num) |
Number can be 0 (valid) | No | if (num != null) |
Boolean can be false (valid) | No | if (flag !== undefined) |
| Object/function exists | Yes | if (obj) / if (fn) |
| Array has elements | No | if (arr.length > 0) |
Key takeaways
- 8 falsy values:
false,0,-0,0n,"",null,undefined,NaN— memorize them. - Everything else is truthy, including
[],{},"0","false", and object wrappers. []is truthy, but[] == falseistrue— this is the==coercion algorithm, not truthiness.- Use
!!valueorBoolean(value)for explicit boolean conversion. - Common bugs:
0,"", andfalseas valid values being replaced by fallbacks when using||orif (!x). - Use
??instead of||when0,"", orfalseare legitimate values. - Check array emptiness with
.length, not truthiness. Check object emptiness withObject.keys().length. - When in doubt, use explicit comparisons (
=== null,!== undefined,.length > 0).
Explain-It Challenge
Explain without notes:
- List all 8 falsy values in JavaScript.
- Why is
[]truthy but[] == falseevaluates totrue? Walk through the coercion steps. - Give an example where using
if (!value)creates a bug because0is a valid value. - What is the difference between
!!xandBoolean(x)? - When should you use
value ?? "default"instead ofvalue || "default"?
Navigation: ← 1.18.e — typeof Operator · 1.18.g — Type Coercion →