Truthy and falsy values are the rules JavaScript uses to treat any value as true or false in a condition. There are eight falsy values, namely false, 0, -0, 0n, the empty string “”, null, undefined and NaN. A falsy value counts as false wherever the language expects a boolean, such as an if condition or a ternary. Every other value is truthy and counts as true, including the string “0”, the string “false”, an empty array and an empty object. TypeScript compiles to JavaScript, so the same rules apply there.
Short conditions like if (name) rely on truthy and falsy values, and so do the || and && operators. The short forms save code, but they also cause one of the most common bugs in JavaScript and TypeScript, where a valid 0 or empty string is treated as “missing”. For example, a shop page shows “unknown” instead of “0 items” for an empty cart.
The following example tests values with the Boolean() function, which returns the same result that an if condition uses. The results come from Node.js 22 with TypeScript 7.0.2.
// 1. The eight falsy values
const falsy = [false, 0, -0, 0n, "", null, undefined, NaN];
const allFalsy = falsy.every((v) => !v); // allFalsy = true
// 2. Truthy values that look empty or false
const t1 = Boolean("0"); // t1 = true
const t2 = Boolean("false"); // t2 = true
const t3 = Boolean([]); // t3 = true
const t4 = Boolean({}); // t4 = true
// 3. Convert to a boolean
const fruit = "apple";
const b1 = !!fruit; // b1 = true
const b2 = Boolean(0); // b2 = false
// 4. A truthiness check narrows the type
function shout(name?: string): string {
return name ? name.toUpperCase() : "NO NAME"; // name: string in the true branch
}
const s = shout("raj"); // s = "RAJ"
// 5. 0 is falsy: || replaces it, ?? keeps it
const count = 0;
const c1 = count || 10; // c1 = 10
const c2 = count ?? 10; // c2 = 0
Notice that the string “0” and the empty array are truthy, whereas the number 0 is falsy and || replaces it.
Next, we go through the falsy and truthy values and see where the conversion happens and how to convert explicitly. After that, we look at how TypeScript narrows types with truthiness checks and which mistakes it reports, and we finish with the pitfalls around 0, empty arrays and filter(Boolean).
1. The Falsy Values
The ECMAScript specification defines the conversion to a boolean in its ToBoolean operation, and the list of values that become false is short and fixed. Any expression that evaluates to one of them is falsy too, so 2 – 2 is falsy because the result is 0.
| Value | Type | Note |
|---|---|---|
| false | boolean | The boolean itself |
| 0 and -0 | number | Positive and negative zero |
| 0n | bigint | BigInt zero |
| “” | string | Empty string; also ” and an empty template string |
| null | null | Intentional “no value” |
| undefined | undefined | Value never set |
| NaN | number | “Not a number”, for example the result of 0 / 0 |
const f1 = Boolean(false); // f1 = false
const f2 = Boolean(0); // f2 = false
const f3 = Boolean(-0); // f3 = false
const f4 = Boolean(0n); // f4 = false, BigInt zero
const f5 = Boolean(""); // f5 = false, empty string
const f6 = Boolean(null); // f6 = false
const f7 = Boolean(undefined); // f7 = false
const f8 = Boolean(NaN); // f8 = false
const f9 = Boolean(2 - 2); // f9 = false, result is 0
Browsers have one more falsy value, document.all, an old object kept only for compatibility with very old web pages. It does not exist in Node.js, and we never use it in new code.
2. Everything Else Is Truthy
Any value not in the falsy list is truthy. Strings that look false and numbers other than zero confuse developers who come from Java, where only a boolean can be used as a condition, and so do objects that look empty.
// 1. Non-empty strings, even "0", "false" and " "
const s1 = Boolean("0"); // s1 = true
const s2 = Boolean("false"); // s2 = true
const s3 = Boolean(" "); // s3 = true
// 2. Every number except 0, -0 and NaN
const n1 = Boolean(-1); // n1 = true
const n2 = Boolean(Infinity); // n2 = true
// 3. Every object, even empty or "false" ones
const o1 = Boolean([]); // o1 = true
const o2 = Boolean({}); // o2 = true
const o3 = Boolean(new Boolean(false)); // o3 = true, an object
const o4 = Boolean(() => false); // o4 = true, a function
The rule for objects has no exceptions, so arrays, plain objects, functions, dates and wrapper objects such as new Boolean(false) are all truthy, whatever they contain. A string from a form field or a query parameter, such as “false” or “0”, is truthy too. To read it as a boolean or a number, we compare it (value === “true”) or parse it (Number(value)).
3. Where JavaScript Converts a Value to a Boolean
JavaScript converts the value to a boolean in every place that expects a condition.
- The conditions of if, while and for statements.
- The ternary operator ?:.
- The NOT operator !.
- The callback result of array methods such as filter(), some() and every().
The && and || operators also test truthiness, but they do not return a boolean. They return one of their operands. The expression a && b returns either a when a is falsy or b otherwise. The expression a || b returns either a when a is truthy or b otherwise.
const fruits = ["apple", "", "banana"];
// 1. if, ternary and loop conditions
const label = fruits.length ? "has fruits" : "empty"; // label = "has fruits"
// 2. The callback result of filter()
const named = fruits.filter((f) => f); // named = ["apple", "banana"]
// 3. && and || return one of the operands
const first = fruits[0]; // "apple"
const empty = fruits[1]; // ""
const a = first && first.length; // a = 5
const b = empty && empty.length; // b = ""
const c = empty || "none"; // c = "none"
The value of b is the empty string, not false, so code that expects a number or a boolean from && can get “” or 0 instead. The same operand rule applies to all logical operators that short-circuit.
4. Converting a Value to a Boolean
When we need a real true or false, for example as a function result or a JSON field, we convert the value explicitly. A REST API that returns “hasDiscount”: “” instead of false breaks clients that check the type, so the conversion matters there. Both ways of converting give the same result.
const fruit = "apple";
const empty = "";
const age: number | null = 37;
// 1. Boolean() function
const b1 = Boolean(fruit); // b1 = true
const b2 = Boolean(empty); // b2 = false
// 2. Double NOT operator
const b3 = !!age; // b3 = true
const b4 = !!empty; // b4 = false
// 3. Typed result for a function
function hasName(name: string | undefined): boolean {
return Boolean(name);
}
const h = hasName(""); // h = false
The first ! converts the value to a boolean and negates it, and the second ! negates it back. The function Boolean(value) does the same and is easier to read while we are learning. We never use new Boolean(value), because it creates a wrapper object, and every object is truthy, as section 2 showed.
5. Truthiness Checks in TypeScript
TypeScript understands truthiness checks and narrows types with them. Inside if (name), a parameter of type string | undefined becomes string, because undefined is falsy. Truthiness narrowing is convenient, but it also removes the empty string, and for numbers it removes 0. The compiler does not warn about that, because it cannot know whether 0 is a valid value in our program.
// 1. Narrowing string | undefined
function shout(name?: string): string {
if (name) {
return name.toUpperCase(); // name: string
}
return "NO NAME"; // also reached for ""
}
const s1 = shout("raj"); // s1 = "RAJ"
const s2 = shout(""); // s2 = "NO NAME"
// 2. Bug: 0 is a valid count but falsy
function wrong(count?: number): string {
return count ? count + " items" : "unknown";
}
const w = wrong(0); // w = "unknown"
// 3. Fix: check for undefined only
function right(count?: number): string {
return count !== undefined ? count + " items" : "unknown";
}
const r = right(0); // r = "0 items"
For strings, a truthiness check is often what we want, since an empty name is as useless as a missing one. For numbers, we either compare with undefined or null, or we use ?? for default values. Both ?? and the value == null check treat null and undefined the same way, as explained in undefined vs null.
TypeScript does report conditions that can never change. Since TypeScript 5.6, a literal that is always truthy or always falsy in a condition is an error, and an uncalled function in an if has been an error for longer, because both point to a typing mistake in most cases.
function isReady(): boolean {
return true;
}
if (isReady) {
console.log("ready");
}
const flag = !!"apple";
const name = "" || "none";
src/err.ts(5,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
src/err.ts(9,16): error TS2872: This kind of expression is always truthy.
src/err.ts(10,14): error TS2873: This kind of expression is always falsy.
The first error catches if (isReady) where we meant if (isReady()). A function object is always truthy, so without the error, the block would always run. The compiler does not report the same mistake for an array variable, so if (fruits) compiles even though an array is always truthy.
6. Common Truthy and Falsy Bugs
Most truthiness bugs come from values that are falsy but valid, such as 0 and “”, or from objects that are truthy but empty. The table pairs each mistake with the check that expresses what the code means.
| Intent | Wrong check | Right check |
|---|---|---|
| A count was given | if (count) | if (count !== undefined) |
| Default only for missing values | count || 10 | count ?? 10 |
| The array has elements | if (fruits) | if (fruits.length > 0) |
| The object has properties | if (stock) | if (Object.keys(stock).length > 0) |
| Remove only null and undefined | list.filter(Boolean) | list.filter((v) => v != null) |
When we run the empty-collection checks, the empty array still passes the truthiness check. For example, an order list that removes empty entries with filter(Boolean) also removes an order with quantity 0.
const fruits: string[] = [];
const stock: Record<string, number> = {};
// 1. Empty arrays and objects are truthy
const listCheck = fruits ? "yes" : "no"; // listCheck = "yes"
const isEmptyList = fruits.length === 0; // isEmptyList = true
const isEmptyObject = Object.keys(stock).length === 0; // isEmptyObject = true
// 2. filter(Boolean) also removes 0
const ages = [37, 0, 40, null];
const truthyOnly = ages.filter(Boolean); // truthyOnly = [37, 40]
const present = ages.filter((a) => a != null); // present = [37, 0, 40]
The filter(Boolean) call has a second problem in TypeScript, because the result type stays (number | null)[], as Boolean is not a type guard. The arrow function (a) => a != null keeps 0 and, since TypeScript 5.5, gives the result type number[]. The same type inference works for other array filter callbacks that check for null.
7. Running the Truthy and Falsy Checks
The truthy-and-falsy project on GitHub has one file per section and an index.ts that prints the results from the snippet comments. It runs on Node.js 22 or newer with TypeScript 7.0.2.
npm install
npm start
8. Conclusion
Eight values are falsy in JavaScript and TypeScript, namely false, 0, -0, 0n, “”, null, undefined and NaN. Everything else is truthy, including “0”, “false”, [] and {}. Conditions, !, &&, || and array callbacks convert values with the truthy and falsy rules, and Boolean() or !! does it explicitly.
TypeScript narrows types with truthiness checks and reports always-true conditions, but it does not stop a check from treating a valid 0 or “” as missing. For numbers, collections and default values, explicit checks such as !== undefined, .length > 0 and ?? say what the code means.
9. References
MDN describes truthy and falsy values and the specification defines the conversion, whereas the TypeScript Handbook and release notes cover narrowing and the newer checks.
- MDN: Falsy
- MDN: Truthy
- MDN: Boolean() constructor
- ECMAScript specification: ToBoolean
- TypeScript Handbook: Truthiness narrowing
- TypeScript 5.6 release notes: disallowed nullish and truthy checks
Happy Learning !!
0 == “0” //true
0 == [] //true
“0” == [] //false
Why “0”==[] is false in javascript?
Because String([]) = “”
meaning “0” == “” -> false
From ECMAScript spec:
“If Type(x) is Number and Type(y) is String,
return the result of the comparison x == ToNumber(y).” – it resolves first case to true
“If Type(x) is either String or Number and Type(y) is Object,
return the result of the comparison x == ToPrimitive(y).” – type of [] is object, JS compares only primitives, so [] is converted to 0, it resolves 2nd case
The same rule resolves third case because String([]) resolves to empty string (as Stefan mentioned).