· tips · 6 min read
The Surprising Truth About JavaScript's Truthy and Falsy Values
Truthy and falsy values in JavaScript are simple in theory but full of practical traps. Learn the real list of falsy values, see the gotchas that cause bugs in production, and adopt safer patterns like ?? and explicit checks.

Outcome first: by the end of this post you’ll be able to spot-before they ship-the small truthiness mistakes that lead to big bugs. You’ll also have clear fixes and patterns to avoid them.
Why this matters. Truthy/falsy checks are used everywhere: validating inputs, picking defaults, deciding control flow. One mistaken assumption is all it takes to break a form, mis-handle 0 values, or silently ignore an API response. Read on for the surprises and the fixes.
Quick definition - what does “truthy” and “falsy” mean?
In JavaScript any value used in a boolean context (like if (...), &&, ||, or !) is converted to either true or false. If it converts to false, we say the value is “falsy”. Everything else is “truthy.” Use !!value or Boolean(value) to see how a value coerces.
A short demonstration:
console.log(Boolean(0)); // false
console.log(Boolean('')); // false
console.log(Boolean([])); // true
console.log(Boolean({})); // true
console.log(Boolean('0')); // trueMDN has a compact reference of falsy values if you want to bookmark it: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
The canonical list of falsy values (the ones you must remember)
There are only a few falsy values in JavaScript:
false0and-00n(BigInt zero)""(empty string)nullundefinedNaN
Everything else is truthy. Yes, even an empty array [] and an empty object {} are truthy.
Surprising truthy/falsy gotchas and real-world examples
Below are patterns I see cause issues again and again. For each one: what happens, why it surprises people, and what to do instead.
1) Using || for defaults - and losing valid 0 or empty-string values
Problem:
const price = response.price || 10; // developer expects price to be 0 if response.price === 0If response.price is 0, the || operator treats 0 as falsy and returns 10. That’s a classic bug when 0 is a valid value (free item, zero-count, offset=0, etc.).
Fix: use the nullish coalescing operator ??, which only treats null and undefined as “missing”:
const price = response.price ?? 10; // preserves 02) indexOf and -1 truthiness trap
Problem:
const idx = arr.indexOf(item);
if (idx) {
// assume item found
}If item is not found, indexOf returns -1. But -1 is truthy. So the if (idx) branch runs when the item is missing. This is a common off-by-one style logical bug.
Fixes:
- Use
includesfor presence checks:if (arr.includes(item)) { ... }(clear and intention-revealing). - Or check explicitly:
if (idx !== -1) { ... }.
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
3) Empty arrays and objects are truthy
Problem:
const arr = [];
if (!arr) {
// won't run - empty array is truthy
}
if (arr.length) {
// only true when non-empty
}Developers migrating from other languages sometimes expect empty containers to be falsy. In JS they are truthy. If you want to know whether an array is empty, test arr.length explicitly.
4) The string “0” is truthy (and surprises people from PHP or shell backgrounds)
'0' is a non-empty string, therefore truthy. In some languages (notably PHP) '0' is considered falsy, so this trips people switching to JavaScript.
if ('0') {
console.log('this runs'); // prints
}Be explicit when a numeric string is possible. Convert before checking: Number(s) or test against ''.
5) null, undefined, and API responses
null and undefined are falsy. That’s expected. But subtle bugs arise when you conflate “no value” with 0 or ''. Use == null to check for either null or undefined at once (value == null is true for both). But prefer explicit checks or ?? for defaults.
Also be careful: typeof document.all is an example of a special host object that behaves oddly (historical legacy behavior). See https://developer.mozilla.org/en-US/docs/Web/API/Document/all
6) NaN is falsy - but you often see it silently
When number ops produce NaN, if (value) will treat it as falsy. But sometimes NaN propagates silently and later logic expects numbers. Use Number.isNaN(value) to detect it explicitly.
7) Logical operators return values - not just true/false
|| and && return one of their operands, not a boolean. This is often used deliberately for defaults (a || b) but can surprise:
console.log('' || 'fallback'); // 'fallback'
console.log(0 || 42); // 42
console.log('foo' && 'bar'); // 'bar'Remember that these operators don’t coerce to booleans first and then return true/false; they return the evaluated operand.
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_operators
8) Beware mixing ?? with || / && without parentheses
The ?? operator has lower precedence than || and && in some expressions and mixing them can throw a syntax error without parentheses. Are you using a || b ?? c? Group explicit intent: (a || b) ?? c or a || (b ?? c) so the behavior is clear.
9) 0n (BigInt zero) is falsy - yes, BigInt obeys truthiness too
A 0n BigInt is falsy, just like 0. Keep that in mind when your code accepts BigInt values.
Practical patterns and checklists to avoid bugs
- Default safely: use
const v = x ?? defaultValuewhen0or''are valid. - Test array emptiness with
arr.length, notif (arr). - For membership checks prefer
includesor explicitindex !== -1. - Use
Number.isNaN()to detectNaNexplicitly. - Convert and validate types early. If a function expects a number, coerce and assert.
- Use
!!valuewhen you explicitly want the boolean-converted form, andBoolean(value)if you want clearer intent. - Prefer explicit comparisons for important conditions:
if (count > 0) { ... }is clearer thanif (count) { ... }for numeric checks.
Short cookbook: common situations and quick fixes
- Default to 10 if value is missing but allow 0:
const v = value ?? 10;- Check whether an element exists in an array:
if (arr.includes(item)) { ... }
// or
if (arr.indexOf(item) !== -1) { ... }- Verify non-empty string:
if (typeof s === 'string' && s.length > 0) { ... }- Avoid ambiguous boolean coercion in conditionals on numeric results:
const idx = arr.indexOf(item);
if (idx >= 0) {
/* found */
}Where to read more
- The MDN glossary on falsy values: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
- Nullish coalescing operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
- Logical operators and how they return operands: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_operators
document.allhistorical oddity: https://developer.mozilla.org/en-US/docs/Web/API/Document/all
Final takeaway
Truthy and falsy are simple concepts with outsized impact. They let you write short, expressive code. But they also let small assumptions silently break behavior-especially when 0, '', -1, NaN, or empty structures are valid inputs. Use explicit checks, prefer ?? for defaults when appropriate, and validate early. Small clarity in your conditionals prevents big surprises later.
Treat truthiness as a helpful shorthand - not as the only guard against invalid or edge-case values.



