---
title: "JavaScript Falsy Values: All 8, Plus Why [] Is Truthy"
description: "JavaScript has eight falsy values: false, 0, -0, 0n, an empty string, null, undefined and NaN. Empty arrays and objects are truthy. The edge cases, explained."
canonical: "https://www.trevorlasn.com/blog/javascript-truthy-and-falsy"
author: "Trevor I. Lasn"
published: "2024-10-27"
last-updated: "2024-11-01"
topics: ["JavaScript"]
---

# JavaScript Falsy Values: All 8, Plus Why [] Is Truthy

JavaScript has eight falsy values: false, 0, -0, 0n, an empty string, null, undefined and NaN. Empty arrays and objects are truthy. The edge cases, explained.

By Trevor I. Lasn · Published 2024-10-27 · Updated 2024-11-01 · https://www.trevorlasn.com/blog/javascript-truthy-and-falsy

Working with JavaScript's truthy and falsy values used to trip me up. Sure, I knew the basics - `false` is falsy, `true` is truthy. But the edge cases? Those were tricky. Let's dive into what I've learned about this fundamental yet often misunderstood concept.

### The Eight Falsy Values

In JavaScript, there are exactly eight falsy values. Everything else is truthy. Here they are:

```js title="JavaScript"
false       // The boolean false
0           // The number zero
-0          // Negative zero
0n          // BigInt zero
""          // Empty string
null        // Absence of any value
undefined   // Uninitialized value
NaN         // Not a Number
```

<div
  class="flex items-center text-sm pl-4 pr-2 mb-5 mt-5 text-indigo-700 rounded-lg bg-surface dark:text-indigo-300"
  role="note"
>
  <svg
    class="flex-shrink-0 inline w-4 h-4 me-3"
    aria-hidden="true"
    xmlns="http://www.w3.org/2000/svg"
    fill="currentColor"
    viewBox="0 0 20 20"
  >
    <path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
  </svg>
  <span class="sr-only">Info</span>
  <div>
    <p class="font-medium text-md mb-1">
      `document.all` is a unique case: it's an object that JavaScript treats as falsy for historical reasons. This legacy DOM API was used in old Internet Explorer versions to access all elements.
      </p>
  </div>
</div>

### The Edge Cases That Break Your Brain

Type coercion in JavaScript creates some truly mind-bending scenarios. 
```js title="JavaScript"
// Arrays
[] == false     // true - empty array is truthy but converts to 0
[1,2] == true   // false - non-empty array is truthy but doesn't equal true
```

The empty array comparison `[] == false` evaluates to `true`, yet an empty array is actually truthy. This happens because JavaScript first converts the array to a primitive value. When an empty array is converted to a primitive, it becomes an empty string. That empty string then converts to `0`, and `0 == false` is `true`. Yes, really.

Non-empty arrays like `[1,2] == true` evaluate to `false` through a similar conversion maze. The array `[1,2]` becomes the string `"1,2"` when converted to a primitive. This string can't be cleanly converted to a number, so it becomes `NaN`. And `NaN == true` is `false`. The fact that `[1,2]` is truthy when used in an `if` statement doesn't help it equal `true` in this comparison.

Objects throw another curveball. `{} == false` is `false` because an empty object converts to `"[object Object]"` when coerced to a primitive. This string can't be converted to a number, so it becomes `NaN`, making the comparison `false`. Yet new `Boolean(false)` creates a Boolean object wrapping the value `false`, which bizarrely equals `false` when compared with `==`, even though the Boolean object itself is truthy in conditional statements.

```js title="JavaScript"
// Objects
{} == false     // false - empty object is truthy
new Boolean(false) == false  // true - but the object itself is truthy!
```

String comparisons add their own layer of confusion. `"0" == false` is `true` because the string `"0"` gets converted to the number `0`, which equals `false` after type coercion. However, `"false" == false` is `false` because the string `"false"` can't be converted to a number - it becomes `NaN`, making the equality check fail. The string content being "false" is irrelevant to the actual boolean value `false`.

```js title="JavaScript"
// Strings
"0" == false    // true - string "0" converts to number 0
"false" == false // false - string "false" doesn't convert to boolean false
```

These quirks explain why the strict equality operator (`===`) is generally preferred - it avoids this type coercion chaos entirely. But understanding these edge cases remains crucial for debugging legacy code or working with systems where loose equality is still in use.

Here's a deceptively simple authentication check that could cause issues in production:

```js title="JavaScript"
function isUserLoggedIn(user) {
  return user.loggedIn;  // Prone to errors
}
```

This code has two significant problems. First, If `user` is `undefined`, accessing `user.loggedIn` throws a TypeError. Secondly, The function returns whatever value is in `loggedIn` directly - could be `0`, `''`, `undefined`, `null`, or any other value. The type coercion would actually happen at the call site, not in this function.

A more robust implementation handles these edge cases explicitly:

```js title="JavaScript"
function isUserLoggedIn(user) {
  if (!user) return false;
  return Boolean(user.loggedIn); // Explicit conversion
}
```

The improved version first checks if `user` exists, preventing the TypeError. Then it explicitly converts `user.loggedIn` to a boolean with `Boolean()`, making the intention clear and avoiding unexpected type coercion. This pattern is particularly important in authentication flows where ambiguity can lead to security issues.

### Best Practices I've Learned

Type coercion affects code in surprising ways. Here are patterns I use to prevent bugs:

```js title="JavaScript"
function getUserRole(user) {
  if (!user?.role) {
    return 'guest';
  }
  return user.role;
}
```

The optional chaining (`?.`) here prevents crashes when `user` is `null` or `undefined`. Without it, `user.role` would throw a TypeError. The `!` operator then handles these cases:

* `null` or `undefined` user → returns 'guest'
* `user.role` is `undefined` → returns 'guest'
* `user.role` is empty `string` → returns 'guest'
* `user.role` is `0` → returns 'guest'
* `user.role` has value → returns that value

### Array Length Validation

This seemingly simple function handles several edge cases:

```js title="JavaScript"
function hasActiveItems(items) {
  return Boolean(items?.length);
}
```

* `items` is `undefined` → returns `false`
* `items` is `null` → returns `false`
* `items` is `[]` → returns `false` (length is 0)
* `items` is `[1,2,3]` → returns `true` (length is 3). 

The `Boolean() `conversion ensures we always return a true boolean instead of a number.

### Number Validation

<div class="pt-2" />

```js title="JavaScript"
function isValidScore(score) {
  return typeof score === 'number' && !Number.isNaN(score);
}
```

This function catches common numeric pitfalls:

* `"123"` → returns `false` (string)
* `undefined` → returns `false`
* `NaN` → returns `false`
* `0` → returns `true`
* `100` → returns `true`

The `typeof` check ensures we have an actual number, not a string or other type that might coerce to a number.

### Safe Configuration Object

<div class="pt-2" />

```js title="JavaScript"
function getConfig(settings) {
  const config = {
    theme: settings?.theme ?? 'default',
    timeout: settings?.timeout ?? 5000,
    retries: settings?.retries ?? 3
  };
  return config;
}
```

The nullish coalescing operator (`??`) here is great because it only falls back to the default when a value is `null` or `undefined`

* `settings` is `undefined` → uses all defaults
* `settings.theme` is `""` → keeps empty string (unlike `||`)
* `settings.timeout` is `0` → keeps `0` (unlike `||`)
* `settings.retries` is `null` → uses default 3
* `settings.retries` is `undefined` → uses default 3

The combination of `?.` and `??` makes this function extremely robust - it will never throw and always returns a complete config object, while still respecting intentionally set falsy values.

Note: If you're not familiar with the nullish coalescing operator (`??`), you can read more about it at [JavaScript Operators: '||' vs '&&' vs '??'](https://www.trevorlasn.com/blog/javascript-logical-operators)

### Key Takeaways for Working with Truthy/Falsy

JavaScript's type coercion doesn't have to be a source of bugs. Here's what matters:

```js title="JavaScript"
// Use strict equality by default
value === false    // instead of value == false

// Be explicit with boolean conversions
Boolean(someValue) // instead of !!someValue

// With || (buggy)
const volume = settings?.volume || 100  
// 0 → 100 (bug: muted becomes max volume!)
// '' → 100
// false → 100
// null → 100
// undefined → 100

// Only use defaults for null/undefined
const volume = settings?.volume ?? 100
// 0 → 0 (correct: stays muted)
// '' → ''
// false → false
// null → 100
// undefined → 100
// a volume setting of 0 is (muted), while null or undefined would indicate a missing setting that should default to 100.
// `??` falls back only on null/undefined, '||' falls back on all falsy values
```

Next time you're debugging a mysterious boolean condition or writing a validation function, remember: being [explicit](https://www.trevorlasn.com/blog/explicit-is-better-than-implicit) about your intentions with types will save you hours of debugging later.
