Skip to content
academia.sh

Lesson 10 / 21

Truthy and Falsy Values

The to-boolean conversion procedure, the eight values counted as false, logical operators returning a value, nullish coalescing, and optional chaining.

Contents

The previous two lessons defined two coercion procedures: converting to number and to string. The third, converting to boolean, is the foundation of conditional constructs and answers a question deferred across two lessons: why was the value 0 mistaken for missing data?

The procedure itself is short — there is no page of rules, there is a list of eight values. The real subtlety is not in the list, it is in how logical operators use that list.

The Eight Values Counted as False

The to-boolean conversion procedure is defined by a list: the following eight values are counted as false (falsy), every other value is counted as true.

const falsyValues = [
  ["false", false],
  ["0", 0],
  ["-0", -0],
  ["0n", 0n],
  ['""', ""],
  ["null", null],
  ["undefined", undefined],
  ["NaN", NaN],
];

for (const [label, value] of falsyValues) {
  console.log(label.padEnd(10), Boolean(value));
}
false      false
0          false
-0         false
0n         false
""         false
null       false
undefined  false
NaN        false

The list is closed: no value not on it is counted as false. This is a more useful rule than memorizing the list, because it resolves counterintuitive examples directly:

console.log(Boolean("0"), Boolean("false"), Boolean(" "));
console.log(Boolean([]), Boolean({}));
console.log(Boolean(-1), Boolean(Infinity));
true true true
true true
true true

Every non-empty string is counted as true — regardless of its content. The texts "0" and "false" fall under this rule too; they are not converted to number or boolean. An empty array and an empty object are also counted as true: objects are not on the list, so all of them are true. Every number besides zero, including negatives and infinity, is counted as true.

One point deserves attention: Boolean([]) gives true, while the previous lesson’s [] == false comparison gave true too. There is no contradiction — two different procedures are running. Loose equality converts the array to a primitive value, then to a number; converting to boolean never looks inside the object at all. The same value giving a different result in two different contexts makes it necessary to know which procedure is being called.

Logical Operators Do Not Return a Boolean

The && and || operators, despite their names, do not produce true or false. They return one of the operands:

console.log(0 || "fallback");
console.log("A1" || "fallback");
console.log(0 && "never");
console.log("A1" && 21.5);
fallback
A1
0
21.5

The rule is this. The || operator evaluates the left operand; if it is counted as true, it returns it, otherwise it returns the right operand. The && operator evaluates the left operand; if it is counted as false, it returns it, otherwise it returns the right operand.

The right operand is evaluated only when needed. This is the short-circuit evaluation defined in the Operators and Expressions lesson of the Programming Fundamentals course. In the third line, "never" was never evaluated at all.

If the result needs to be forced to boolean type, the conversion is written explicitly. Because conditional constructs already convert to boolean, this is usually unnecessary.

The Default-Value Trap

The || operator’s most common use is putting a fallback in place of a missing value. This use breaks down when a falsy value is valid data:

const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: 0 },
  { station: "B1" },
];

for (const record of records) {
  const viaOr = record.temperature || -273.15;
  const viaNullish = record.temperature ?? -273.15;
  console.log(record.station, "|| ->", viaOr, " ?? ->", viaNullish);
}
A1 || -> 21.5  ?? -> 21.5
A2 || -> -273.15  ?? -> 0
B1 || -> -273.15  ?? -> -273.15

The middle line is the answer to the question left open across two lessons. Station A2’s temperature is 0; this is a valid measurement and it is present in the record. Still, the || operator counted it as false and moved to the fallback value. The measurement turned into absolute-zero temperature.

In the bottom line’s B1 record, the temperature field truly does not exist; the value of record.temperature is undefined, and moving to the fallback is correct there. The two lines produced the same result, but only one of them was the intended behavior — the || operator cannot distinguish “there is no value” from “the value is zero.”

The ?? operator — nullish coalescing — moves to the right operand only for null and undefined values:

console.log(0 || "fallback");
console.log(0 ?? "fallback");
console.log("" || "fallback");
console.log("" ?? "fallback");
console.log(null ?? "fallback");
fallback
0
fallback

fallback

The fourth line looking empty is because the ?? operator counts an empty string as a valid value and returns it. The second line, on the other hand, preserved the value 0.

The selection rule is clear: if the meaning “value was never given” is wanted, use ??; if the meaning “value is counted as false” is wanted, use ||. In measurement data, zero, an empty string, and false are most often valid values; this is why default assignments are written with ??.

Optional Chaining

The third operator of the same family shortens the absence check while accessing nested fields:

const record = { station: "A1", location: { elevation: 120 } };
const missing = { station: "B1" };
console.log(record.location?.elevation);
console.log(missing.location?.elevation);
console.log(missing.location?.elevation ?? 0);
120
undefined
0

The ?. operator breaks the chain and returns undefined if the value to its left is null or undefined; otherwise it continues the access. The check’s criterion is the same as ?? — not values counted as false, only the two absence values.

The same access without the operator throws an error:

const missing = { station: "B1" };
try {
  console.log(missing.location.elevation);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
TypeError: Cannot read properties of undefined (reading 'elevation')

The Ways to Run Code lesson showed how to read this message: the problem is not in the elevation field, it is that the value of missing.location is undefined.

Optional chaining should be used carefully. It is the right tool if a field being absent is an expected situation; if it is an unexpected situation, it suppresses the error and carries the problem forward. The difference lies in the data’s contract, not in the syntax.

Application to the Measurement Script

The shortest use of truthy and falsy values is a filtering operation:

const fields = ["21.5", "", "0", "abc", null];
const filled = fields.filter(Boolean);
console.log(filled);
[ '21.5', '0', 'abc' ]

The empty string and null were eliminated; the text "0" was preserved, because it is a non-empty string. This is exactly the intended behavior: what is being filtered here is raw text, not a numeric value.

Applying the same filter to numeric values loses data:

const measurements = [21.5, 0, NaN, 19.75];
console.log(measurements.filter(Boolean));
console.log(measurements.filter((value) => !Number.isNaN(value)));
[ 21.5, 19.75 ]
[ 21.5, 0, 19.75 ]

The first filter dropped a valid zero measurement. The second dropped only what was truly invalid. The rule is that truthy and falsy values are not a general validity criterion: what counts as valid depends on the data’s meaning and has to be written explicitly.

Summary

  • The to-boolean conversion procedure counts eight values as false: false, 0, -0, 0n, an empty string, null, undefined, NaN. Every other value is counted as true.
  • An empty array, an empty object, and strings like "0" are counted as true; objects are not on the list.
  • && and || return one of the operands, not a boolean, and evaluate the right operand only when needed.
  • || moves to the fallback for all eight values; ?? moves only for null and undefined. Default assignments in measurement data are written with ??.
  • ?. breaks the chain only for null and undefined; it should not be used to hide an unexpected absence.

Next Step

The value model is complete: what types exist, how they convert, how they are compared, and how they are evaluated in a condition. Next is building program flow with these values. The next topic will begin with conditional selection: if chains, the ternary operator, and which equality the switch statement uses for comparison — one of the three operators from this lesson.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close