JavaScript Conditionals
Conditionals are how your code makes decisions: if this, then that — otherwise something else. JavaScript gives you if/else if/else, the ternary operator, and short-circuit logical operators. The hard part isn’t the syntax; it’s understanding which values JavaScript counts as “truthy” or “falsy” — get that wrong and you’ll write bugs that hide for months.
if / else if / else
const score = 87;
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else if (score >= 70) {
console.log("C");
} else {
console.log("F");
}
// Output: B
Always use braces, even for single statements. The “saved keystrokes” form is a known footgun:
// ❌ Looks fine, but adding a line later breaks it
if (cond)
doA();
doB(); // always runs — not part of the if!
// ✅ Always braces
if (cond) {
doA();
doB();
}
Truthy and Falsy
The condition expression is coerced to boolean. JavaScript has exactly eight falsy values; everything else is truthy.
| Falsy | Truthy |
|---|---|
false |
true |
0, -0, 0n |
any non-zero number |
"" (empty string) |
any non-empty string, including "0" and "false" |
null |
[] (empty array!) |
undefined |
{} (empty object!) |
NaN |
functions, dates, etc. |
if ([]) console.log("yes"); // yes ← empty array is truthy
if ({}) console.log("yes"); // yes
if ("0") console.log("yes"); // yes ← non-empty string
if ("false") console.log("yes"); // yes
if (0) console.log("yes"); // (no)
if (NaN) console.log("yes"); // (no)
// Quick boolean coercion
Boolean([]); // true
!!""; // false
!!"hello"; // true
Warning: Empty arrays and objects are truthy. To check “is this collection empty?”, check arr.length === 0 or Object.keys(obj).length === 0.
Comparison Operators
| Operator | Compares | Coerces? |
|---|---|---|
=== |
strict equality | ❌ no |
!== |
strict inequality | ❌ no |
== |
loose equality | ✅ yes |
!= |
loose inequality | ✅ yes |
<, >, <=, >= |
order | ✅ yes |
1 === 1; // true
1 === "1"; // false ← strict, no coercion
1 == "1"; // true ← loose, coerces
0 == false; // true ← coerces to 0 == 0
null == undefined; // true ← special-case
null === undefined; // false
NaN === NaN; // false ← always
Tip: Use === and !== exclusively. The two exceptions where loose == is acceptable: comparing against null to also catch undefined (x == null). Otherwise, never.
// Idiomatic null+undefined check
if (value == null) {
// value is null OR undefined
}
// Or use ?? if you want a fallback
const name = input ?? "guest";
Logical Operators — and Short-Circuit
true && false; // false
true || false; // true
!true; // false
&& and || don’t return booleans — they return one of the operands:
"a" && "b"; // "b" — both truthy → returns last
0 && "b"; // 0 — first falsy → returns it
"a" || "b"; // "a" — first truthy
"" || "fallback"; // "fallback" — first truthy
null || 0 || "x"; // "x" — keeps walking until truthy
Three patterns you’ll see daily:
// Default value (legacy)
const name = userName || "guest"; // ⚠ replaces "" or 0 too!
// Default value (modern, ES2020) — only on null/undefined
const name = userName ?? "guest";
// Short-circuit "if x then run y"
isLoggedIn && showDashboard();
// Optional chaining
user?.profile?.bio;
| Operator | Falls through on | Use when |
|---|---|---|
\|\| |
falsy (incl. 0, "", false) |
“any falsy → fallback” |
?? |
only null / undefined |
“missing → fallback” |
Warning: userInput || defaultValue overrides legitimate 0 or "". If those are valid values, use ?? — that’s its whole reason for existing.
Ternary Operator
A compact if/else that returns a value:
const status = age >= 18 ? "adult" : "minor";
// Nested (use sparingly)
const grade =
score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" : "F";
Use ternary when:
- ✅ Both branches produce a value (not side effects)
- ✅ The whole expression fits clearly on one or two lines
Don’t use ternary when:
- ❌ Branches do side effects (
a > b ? doThis() : doThat()) — useif - ❌ It nests three deep — use
ifchain or a lookup table
Lookup Tables — When You Have Many Branches
If your if/else if chain is just mapping one value to another, use an object:
// ❌ verbose
function grade(score) {
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else return "F";
}
// ✅ data-driven
const GRADE_MAP = [
[90, "A"],
[80, "B"],
[70, "C"],
[0, "F"],
];
function grade(score) {
return GRADE_MAP.find(([min]) => score >= min)?.[1];
}
// ✅ flat key map
const ROLE_LABEL = {
admin: "Administrator",
user: "Standard User",
guest: "Guest",
};
const label = ROLE_LABEL[role] ?? "Unknown";
Guard Clauses
Avoid deep nesting by returning early when a precondition fails:
// ❌ Nested
function process(input) {
if (input != null) {
if (input.length > 0) {
if (input.valid) {
// …actual work, 3 levels deep
}
}
}
}
// ✅ Guard clauses
function process(input) {
if (input == null) return;
if (input.length === 0) return;
if (!input.valid) return;
// actual work, 0 levels deep
}
A Realistic Example — Form Field Validator
function validateEmail(value) {
if (value == null || value === "") {
return { ok: false, error: "Email is required" };
}
const trimmed = value.trim();
if (trimmed.length > 254) {
return { ok: false, error: "Email too long" };
}
const looksLikeEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed);
if (!looksLikeEmail) {
return { ok: false, error: "Invalid email format" };
}
return { ok: true, value: trimmed };
}
validateEmail(""); // { ok: false, error: "Email is required" }
validateEmail("not-an-email"); // { ok: false, error: "Invalid email format" }
validateEmail(" ada@x.io "); // { ok: true, value: "ada@x.io" }
Guard clauses, == for null/undefined, regex for shape, trimmed value returned. Each branch is short and the happy path is at the bottom.
Common Mistakes
// ❌ Truthy-check on a number that could be 0
if (count) renderCount(count); // skips zero!
// ✅ Be explicit
if (count != null) renderCount(count);
if (count !== 0) renderCount(count);
// ❌ Loose equality with strings/numbers
"10" == 10; // true ← surprising
"" == false; // true
[] == false; // true
[1] == 1; // true
// ✅ Always === / !==
// ❌ Returning undefined from a ternary side effect
const x = condition ? foo() : null;
// foo() returns undefined → x is undefined, easy to miss
// ❌ Negated condition for the "common" path
if (!user.isAdmin) {
/* short branch */
} else {
/* long branch */
}
// Flip it — put the common branch first, no negation
Best Practices Checklist
- ✅ Use
===/!==always (exceptx == nullfor null+undefined) - ✅ Use guard clauses to keep code flat
- ✅ Use
??for null/undefined defaults;||only when you genuinely want any falsy to fall through - ✅ Use lookup objects/arrays instead of long if/else chains
- ✅ Always brace the body of
if/else - ❌ Don’t use
if (value)to mean “is set” if0/""/falseare valid - ❌ Don’t nest ternaries past one level
- ❌ Don’t use side-effect ternaries — use
if
What’s Next?
if/else covers most cases. When you have a value to dispatch on with many branches, JavaScript’s switch is occasionally cleaner — but it has its own quirks worth knowing.