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:
false0-00n(BigInt zero)""(an empty string)nullundefinedNaN
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 primitivefalse
đź’ˇ A legacy exception:
document.allis a special browser object deliberately specified as falsy for old web compatibility. It also behaves likeundefinedin 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.
nullandundefinedare loosely equal to each other and not to other ordinary values.- When one operand is a Boolean, JavaScript converts that Boolean to a number:
falsebecomes0, andtruebecomes1. - 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()andtoString(). - 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 asBoolean(value)orNumber(value). - Use
Number.isNaN(value)to detectNaN. - Avoid primitive wrapper constructors such as
new Boolean(),new Number(), andnew 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.