16 minLesson 5 of 35
JavaScript Fundamentals
Control Flow: if, switch, ternary
Control Flow: if, switch, ternary
Control flow determines which code runs and when. JavaScript provides if/else, switch, and the ternary operator for branching, plus some patterns that make your code more readable.
if / else if / else
const temperature = 22;
if (temperature > 30) {
console.log("Hot day — stay hydrated");
} else if (temperature > 20) {
console.log("Nice weather");
} else if (temperature > 10) {
console.log("Chilly — bring a jacket");
} else {
console.log("Cold!");
}
// Single-line form — OK for simple cases, skip the braces
if (isLoggedIn) redirect("/dashboard");
// But always use braces for multi-line blocks
// Avoids the dangling-else bug that caused Apple's SSL vulnerability in 2014
Truthy and Falsy Values
JavaScript converts values to booleans in conditions. Knowing what's falsy is essential:
// Falsy values — evaluate to false in boolean context
false
0
-0
0n // BigInt zero
"" // empty string
null
undefined
NaN
// Everything else is truthy, including:
"0" // non-empty string (even "false"!)
[] // empty array
{} // empty object
-1 // negative number
Infinity
// Common patterns using truthiness
const user = getUserFromDB();
if (user) {
// user exists and is not null/undefined
console.log(user.name);
}
if (items.length) {
// array has at least one element
renderList(items);
}
if (!isLoading && data) {
// loading finished and data is available
renderContent(data);
}
Early Return Pattern
One of the cleanest patterns in JavaScript — return early to avoid deeply nested code:
// Instead of this (nested, hard to read):
function processOrder(order) {
if (order) {
if (order.items.length > 0) {
if (order.payment) {
// actual logic buried here
return completeOrder(order);
} else {
return "Payment required";
}
} else {
return "Cart is empty";
}
} else {
return "No order provided";
}
}
// Use early returns (guard clauses):
function processOrder(order) {
if (!order) return "No order provided";
if (order.items.length === 0) return "Cart is empty";
if (!order.payment) return "Payment required";
// Happy path is unindented and obvious
return completeOrder(order);
}
switch Statement
Use switch when comparing one value against multiple specific cases:
const day = new Date().getDay(); // 0=Sunday, 6=Saturday
switch (day) {
case 0:
console.log("Sunday");
break;
case 1:
console.log("Monday");
break;
case 6:
console.log("Saturday");
break;
default:
console.log("Weekday");
}
// Fall-through (intentional): multiple cases, one action
switch (status) {
case "pending":
case "processing":
showSpinner();
break;
case "complete":
showSuccess();
break;
case "error":
case "failed":
showError();
break;
default:
console.warn("Unknown status:", status);
}
switch uses strict equality (===) for comparison, so types must match exactly.
Ternary Operator
Perfect for simple conditional values — not for complex logic:
// condition ? valueIfTrue : valueIfFalse
const isAdult = age >= 18 ? "Yes" : "No";
const price = isMember ? 9.99 : 19.99;
const greeting = time < 12 ? "Good morning" : "Good afternoon";
// In template literals
console.log(`Status: ${isOnline ? "Online" : "Offline"}`);
// In JSX / HTML generation
const buttonHTML = `<button class="${isActive ? "btn-primary" : "btn-secondary"}">Click</button>`;
// Avoid nesting more than 2 levels
// This is OK:
const grade = score >= 90 ? "A" : score >= 70 ? "B" : "C";
// This is not:
const x = a ? b ? c : d : e ? f : g; // please don't
Logical Operators as Control Flow
// && as a guard: "if left is truthy, evaluate right"
isLoggedIn && showDashboard();
user && user.profile && loadProfile(user.profile.id);
// || as a default: "if left is falsy, use right"
const name = user.name || "Anonymous";
const port = process.env.PORT || 3000;
// In React — conditional rendering
const element = isLoading && <Spinner />;
// ?? for null/undefined specifically
const timeout = config.timeout ?? 5000;
Short-Circuit Evaluation
// && stops at the first falsy value
function riskyOperation() {
console.log("Running!");
return true;
}
false && riskyOperation(); // "Running!" is NEVER logged
true && riskyOperation(); // "Running!" IS logged
// || stops at the first truthy value
"cached" || expensiveComputation(); // computation never runs
// This is why you see patterns like:
const cached = cache.get(key) || (cache.set(key, compute()), cache.get(key));
Nullish Coalescing vs OR
// The OR operator treats 0, "", false as "falsy"
const volume = userSetting || 50;
// Problem: if userSetting is 0 (mute), we get 50 instead!
// The ?? operator only triggers on null/undefined
const volume = userSetting ?? 50;
// Now: 0 stays 0, 50 is used only if setting is null/undefined
Optional Chaining in Conditions
const user = { profile: null };
// Without optional chaining — crashes if profile is null
if (user.profile.avatar) { } // TypeError!
// With optional chaining — safely returns undefined
if (user?.profile?.avatar) {
showAvatar(user.profile.avatar);
}
// With nullish coalescing
const avatar = user?.profile?.avatar ?? "/default-avatar.png";
Practical Example: Form Validation
function validateRegistration(formData) {
const { username, email, password, confirmPassword } = formData;
// Guard clauses for required fields
if (!username?.trim()) return { error: "Username is required" };
if (username.length < 3) return { error: "Username must be 3+ characters" };
if (!email?.includes("@")) return { error: "Valid email required" };
if (!password) return { error: "Password is required" };
if (password.length < 8) return { error: "Password must be 8+ characters" };
if (password !== confirmPassword) return { error: "Passwords don't match" };
// All checks passed
return { success: true, data: { username: username.trim(), email, password } };
}
Next lesson: Loops — for, while, forEach, and for...of for repeating operations efficiently.
📱
Get Notes Free →Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises