· 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.

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')); // true

MDN 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:

  • false
  • 0 and -0
  • 0n (BigInt zero)
  • "" (empty string)
  • null
  • undefined
  • NaN

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 === 0

If 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 0

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

2) 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 includes for 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 ?? defaultValue when 0 or '' are valid.
  • Test array emptiness with arr.length, not if (arr).
  • For membership checks prefer includes or explicit index !== -1.
  • Use Number.isNaN() to detect NaN explicitly.
  • Convert and validate types early. If a function expects a number, coerce and assert.
  • Use !!value when you explicitly want the boolean-converted form, and Boolean(value) if you want clearer intent.
  • Prefer explicit comparisons for important conditions: if (count > 0) { ... } is clearer than if (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

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.

Back to Blog

Related Posts

View All Posts »
Destructuring Function Arguments: A Cleaner Approach

Destructuring Function Arguments: A Cleaner Approach

Learn how to simplify and clarify JavaScript function signatures with parameter destructuring. See side-by-side examples, real-world patterns, pitfalls, and TypeScript usage to write more readable, maintainable code.