Skip to content
All Articles

Mastering Truthy & Falsy in JavaScript: No More Confusion!

Understand Boolean coercion, loose equality, and the JavaScript comparisons that surprise even experienced developers.

4 min read
  • JavaScript
  • Language Fundamentals
  • Type Coercion
On this page
  1. What are truthy and falsy values?
  2. The eight ordinary falsy values
  3. Truthiness is not equality
  4. How loose equality works
  5. Decoding the surprising examples
  6. 1. Empty string and false
  7. 2. The string "true" and true
  8. 3. BigInt and true
  9. 4. Empty array and true
  10. 5. A populated array and true
  11. 6. Plain object and true
  12. 7. Boolean wrapper and false
  13. Key takeaways
  14. Final thought

JavaScript developers at every experience level occasionally stumble over truthy and falsy values, especially when loose equality (==) enters the picture.

Why does [] == true evaluate to false even though an empty array is truthy? Why does "" == false return true?

The key is that Boolean coercion and loose equality are different operations with different rules. Once you keep those rules separate, the results stop feeling arbitrary.

What are truthy and falsy values?

A value is truthy when Boolean(value) produces true, and falsy when it produces false. Conditions such as if (value) perform this Boolean coercion automatically.

The eight ordinary falsy values

JavaScript has eight ordinary falsy values:

  1. false
  2. 0
  3. -0
  4. 0n (BigInt zero)
  5. "" (an empty string)
  6. null
  7. undefined
  8. NaN
if (NaN) {
  console.log("Am I true?")
} else {
  console.log("NaN is falsy")
}

// Output: NaN is falsy

Everything else is truthy, including:

  • All ordinary objects, including {} and []
  • Non-empty strings such as "hello", "0", and "false"
  • Non-zero numbers, negative numbers, and Infinity
  • Functions, symbols, and non-zero BigInts
  • new Boolean(false), because it creates an object rather than the primitive false

đź’ˇ A legacy exception: document.all is a special browser object deliberately specified as falsy for old web compatibility. It also behaves like undefined in a few other operations. You should never rely on it in application code.

Truthiness is not equality

This distinction explains most of the confusion:

Boolean([]) // true
;[] == true // false

The first expression asks JavaScript to convert an array to a Boolean. The second invokes the abstract equality comparison algorithm, which may convert both operands through several steps before comparing them.

How loose equality works

The complete specification algorithm has many branches, but these are the ones you will encounter most often:

  • Values of the same type are compared without cross-type coercion.
  • null and undefined are loosely equal to each other and not to other ordinary values.
  • When one operand is a Boolean, JavaScript converts that Boolean to a number: false becomes 0, and true becomes 1.
  • Strings and numbers are compared numerically, so a numeric string may be converted to a number.
  • When an object is compared with a primitive, JavaScript first converts the object to a primitive using its primitive-conversion behavior—typically through valueOf() and toString().
  • Number and BigInt comparisons follow special numeric rules; JavaScript does not simply convert every BigInt to a Number.

Symbols are not numerically coerced. Two distinct symbols are unequal, while the same symbol reference equals itself:

const id = Symbol("id")

id == id // true
Symbol() == Symbol() // false

In application code, prefer ===. Loose equality is worth understanding, but strict equality makes intent clearer and avoids most implicit conversion.

Decoding the surprising examples

1. Empty string and false

console.log("" == false) // true

The Boolean false becomes 0, and the empty string becomes 0 for the numeric comparison:

"" == false
"" == 0
0 == 0
true

2. The string "true" and true

console.log("true" == true) // false

The Boolean true becomes 1, while Number("true") produces NaN:

"true" == 1
NaN == 1
false

NaN is unequal to every value, including itself. Use Number.isNaN(value) when you need to test for it.

3. BigInt and true

console.log(12n == true) // false

The Boolean becomes the number 1. JavaScript then compares the numeric values represented by 12n and 1. They are different, so the result is false—without needing to turn 12n into a Number first.

4. Empty array and true

console.log([] == true) // false

The Boolean true becomes 1. The array must then become a primitive:

[].valueOf()  // still an array, not a primitive
[].toString() // ""
Number("")    // 0
0 == 1        // false

The empty array is still truthy in a condition. Its truthiness simply is not what == checks here.

5. A populated array and true

console.log([1, 2] == true) // false

The array becomes the string "1,2". That string cannot be converted to a valid number, so the comparison becomes NaN == 1, which is false.

6. Plain object and true

console.log({} == true) // false

A plain object usually becomes the string "[object Object]". Its numeric conversion is NaN, so it cannot equal 1.

7. Boolean wrapper and false

console.log(new Boolean(false) == false) // true

new Boolean(false) is a truthy object, but loose equality converts the wrapper to its primitive value. The comparison eventually reduces to 0 == 0, producing true.

This contrast is a good reason to avoid Boolean wrapper objects:

if (new Boolean(false)) {
  console.log("This still runs because the wrapper is an object.")
}

Key takeaways

  • Truthiness answers one question: “What does this value become in a Boolean context?”
  • Loose equality answers a different question and may perform type coercion before comparing.
  • Objects are truthy in conditions, even when loose equality converts them into surprising primitives.
  • Prefer === and explicit conversions such as Boolean(value) or Number(value).
  • Use Number.isNaN(value) to detect NaN.
  • Avoid primitive wrapper constructors such as new Boolean(), new Number(), and new String().

Final thought

The next time you encounter this collection of quirks:

;[] == false // true
"0" == false // true
null == undefined // true

trace the operands through the abstract equality rules instead of asking whether each value is truthy. That small mental shift turns “JavaScript magic” into a predictable sequence of conversions.

Try the examples in your browser console and change one operand at a time. The fastest way to understand coercion is to observe exactly where the comparison changes.