JavaScript Basics: Truthy and Falsy Values, Equality, and Control Flow
March 21, 2026
In today’s article, I want to focus on a set of JavaScript basics that show up constantly in real-world frontend work:
- How JavaScript treats truthy and falsy values
- Why
===is usually safer than== - How to think clearly about
if/else, ternaries,switch, and short-circuiting - When
??is a better choice than|| - How optional chaining helps avoid runtime errors
These topics seem simple at first, but they are behind a lot of production bugs. Many issues come from accidentally treating valid values like 0, false, or an empty string as “missing.”
Truthy and Falsy Values
In JavaScript, conditionals do not only work with true and false. Many other values are treated as either truthy or falsy.
These are the main falsy values:
false;
0 - 0;
0n;
("");
null;
undefined;
NaN;
Everything else is truthy.
That means this will run:
if ("hello") {
console.log("runs");
}
Because a non-empty string is truthy.
But this will not:
if ("") {
console.log("runs");
}
Because an empty string is falsy.
One important reminder: empty arrays and empty objects are still truthy.
console.log(Boolean([])); // true
console.log(Boolean({})); // true
That surprises a lot of people at first.
Equality: == vs ===
JavaScript gives us two common ways to compare values.
Strict equality, ===, compares both value and type.
console.log(5 === 5); // true
console.log(5 === "5"); // false
Loose equality, ==, allows type coercion.
console.log(5 == "5"); // true
That may look convenient, but it often creates confusing results:
console.log(false == 0); // true
console.log("" == 0); // true
console.log(null == undefined); // true
This is why === is usually preferred in production code. It is stricter, more predictable, and easier to reason about quickly.
A good rule is:
- use
===by default - avoid
==unless you have a very specific reason
Why === Is Preferred in Production Code
The biggest reason === is preferred is predictability.
With ===, JavaScript does not silently convert values behind the scenes. That means your code is easier to read and less likely to surprise you later.
For example:
console.log(false === 0); // false
This is much easier to reason about than:
console.log(false == 0); // true
In production code, clarity usually matters more than clever shortcuts.
Control Flow Refresher
JavaScript gives us several ways to control decision-making in code:
if / else- ternary operator
switch- logical OR
|| - logical AND
&& - nullish coalescing
?? - optional chaining
?.
Each one has a place, but the key is understanding what problem each one is solving.
if / else
This is still the clearest tool when logic has multiple steps.
const age = 20;
if (age >= 18) {
console.log("Adult");
} else {
console.log("Minor");
}
Ternary
A ternary is useful when you want a compact expression.
const status = age >= 18 ? "adult" : "minor";
console.log(status);
It is best when the condition is simple. If the logic gets too nested, if / else is usually easier to read.
switch
A switch can be a nice fit when one value can map to several known cases.
const role = "admin";
switch (role) {
case "admin":
console.log("Has full access");
break;
case "editor":
console.log("Can edit content");
break;
default:
console.log("Limited access");
}
Understanding ||
The || operator is often used for fallback values.
A helpful mental model is this:
|| says: “If the value on the left is falsy, use the value on the right.”
console.log("" || "fallback"); // "fallback"
console.log(0 || 100); // 100
console.log(false || true); // true
console.log(null || "default"); // "default"
console.log(undefined || "default"); // "default"
This can be useful, but it can also be dangerous.
The reason is that || does not only replace missing values like null or undefined. It also replaces valid falsy values like:
0false""
That is where bugs often happen.
Understanding ??
The ?? operator is called the nullish coalescing operator.
Its mental model is narrower:
?? says: “Only use the value on the right if the value on the left is null or undefined.”
console.log("" ?? "fallback"); // ""
console.log(0 ?? 100); // 0
console.log(false ?? true); // false
console.log(null ?? "default"); // "default"
console.log(undefined ?? "default"); // "default"
This is often much safer in production code because it preserves valid falsy values.
That is the key difference:
||cares about falsy values??cares only about missing values
When ?? Beats ||
A simple question helps here:
Do I want a fallback for any falsy value, or only for missing values?
If you want a fallback for all falsy values, || may be okay.
If you only want a fallback when the value is actually missing, ?? is usually better.
For example:
const count = 0;
console.log(count || 10); // 10
console.log(count ?? 10); // 0
If 0 is a real count, then ?? is clearly the better choice.
The same is true for booleans:
const isAdmin = false;
console.log(isAdmin || true); // true
console.log(isAdmin ?? true); // false
If false is the real value, then || incorrectly replaces it.
This is why ?? is often a better default for API data, configuration values, counters, totals, and boolean flags.
Optional Chaining
Optional chaining helps us safely access nested properties without crashing.
For example:
const user1 = {
profile: {
name: "Alex",
},
};
const user2 = {};
console.log(user1.profile?.name); // "Alex"
console.log(user2.profile?.name); // undefined
Without optional chaining, trying to access a missing property could throw an error.
// console.log(user2.profile.name); // TypeError
A useful reminder is that optional chaining only protects the part where you use it.
Safer:
user?.profile?.name;
Less safe:
user?.profile.name;
If profile is missing in the second example, the code can still fail.
A Small Input Normalizer Example
One of today’s exercises was to build a small function that safely handles empty strings, null, undefined, and 0.
A naive version might look like this:
function normalizeInput(value) {
return value || "default";
}
That looks simple, but it has a problem:
console.log(normalizeInput(0)); // "default"
If 0 is a valid value, this is wrong.
A safer version is:
function normalizeInput(value) {
if (typeof value === "string") {
const trimmed = value.trim();
return trimmed === "" ? null : trimmed;
}
return value ?? null;
}
And when tested:
console.log(normalizeInput(" hello ")); // "hello"
console.log(normalizeInput("")); // null
console.log(normalizeInput(" ")); // null
console.log(normalizeInput(null)); // null
console.log(normalizeInput(undefined)); // null
console.log(normalizeInput(0)); // 0
console.log(normalizeInput(false)); // false
This is much safer because it preserves valid falsy values instead of throwing them away.
Common Beginner Mistakes to Avoid
1. Treating all falsy values as missing
Not all falsy values are errors or empty data. 0 and false are often perfectly valid.
2. Using || when ?? is the better fit
This is one of the most common causes of subtle bugs with counts, booleans, and API response values.
3. Using == because it feels shorter
It is shorter, but it is usually less predictable. === is easier to trust in real code.
4. Forgetting empty arrays and objects are truthy
This can cause confusion in conditionals.
if ([]) {
console.log("this runs");
}
This runs.
5. Forgetting optional chaining only protects one step
If you need multiple safe steps, you usually need multiple ?. operators.
Why This Matters in Angular
This topic shows up everywhere in Angular and frontend work.
It matters when:
- rendering optional API data
- showing fallback values in templates
- preserving legitimate values like
0 - normalizing form input
- working with nested response objects
- writing safe component logic
Examples include:
user?.profile?.name- preserving
0in totals or counters - avoiding accidental fallbacks for
false - safely handling partial API responses
These are exactly the kinds of small JavaScript details that make production frontend code more reliable.
Final Takeaway
My biggest takeaway from this refresher is that JavaScript control flow becomes much easier when I stop thinking in vague terms like “empty” and start thinking more precisely.
- falsy does not always mean missing
===is usually safer than==||is for falsy fallback behavior??is for nullish fallback behavior- optional chaining makes nested property access much safer
The more clearly I understand those differences, the fewer bugs I will create when handling real data in frontend applications.
