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 fornullandundefined. Default assignments in measurement data are written with??.?.breaks the chain only fornullandundefined; 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.