Episode 1 — Fundamentals / 1.19 — Conditionals and Loops
1.19.a -- If / Else If / Else Branching
In one sentence: The
ifstatement is JavaScript's primary decision-making tool -- it evaluates a condition as truthy or falsy, runs one branch of code, and can be extended withelse if,else, the ternary operator, and guard clauses to keep logic flat and readable.
Navigation: <-- 1.19 Overview . 1.19.b -- Switch Statement -->
1. The if statement
The simplest conditional: run code only when a condition is truthy.
if (condition) {
// runs when condition is truthy
}
How the condition is evaluated:
JavaScript does not require the condition to be a boolean. It coerces the value to true or false using the truthy / falsy rules (see 1.18):
| Falsy values | Truthy (everything else) |
|---|---|
false | true |
0, -0, 0n | Any non-zero number |
"" (empty string) | Any non-empty string |
null | Objects, arrays (even empty [] and {}) |
undefined | Functions |
NaN |
const username = "Alice";
if (username) {
console.log(`Welcome, ${username}!`); // runs -- non-empty string is truthy
}
2. The else clause
Provide an alternative path when the condition is falsy.
const age = 15;
if (age >= 18) {
console.log("You may enter.");
} else {
console.log("Access denied. You must be 18 or older.");
}
// Output: "Access denied. You must be 18 or older."
Rule of thumb: If the else block does nothing useful, omit it -- dangling empty else blocks add noise.
3. else if chaining
When you have more than two possible paths, chain else if clauses. JavaScript evaluates them top to bottom and enters the first truthy branch.
function getGrade(score) {
if (score >= 90) {
return "A";
} else if (score >= 80) {
return "B";
} else if (score >= 70) {
return "C";
} else if (score >= 60) {
return "D";
} else {
return "F";
}
}
console.log(getGrade(85)); // "B"
console.log(getGrade(42)); // "F"
Important: Order matters. Because the first truthy branch wins, place the most specific (or highest threshold) conditions first.
// BUG -- score >= 60 is true for 85, so this returns "D" for 85
if (score >= 60) { return "D"; }
else if (score >= 70) { return "C"; } // never reached for 85
4. Nested if statements
You can nest if inside another if:
function processOrder(user, cart) {
if (user.isLoggedIn) {
if (cart.items.length > 0) {
if (user.balance >= cart.total) {
placeOrder(user, cart);
} else {
console.log("Insufficient balance.");
}
} else {
console.log("Cart is empty.");
}
} else {
console.log("Please log in first.");
}
}
The problem: Deep nesting (the "pyramid of doom") hurts readability. Every level adds mental load. The fix is guard clauses (Section 8).
5. The ternary operator
A shorthand for simple if / else that produces a value:
condition ? valueIfTrue : valueIfFalse
const age = 20;
const status = age >= 18 ? "adult" : "minor";
console.log(status); // "adult"
Great for:
- Inline assignments
- Template literals
- Return values
function greeting(hour) {
return hour < 12 ? "Good morning" : "Good afternoon";
}
const label = `Status: ${isActive ? "ON" : "OFF"}`;
6. Nested ternaries (and why they harm readability)
You can nest ternaries, but should you?
// Hard to read -- avoid this
const grade = score >= 90 ? "A"
: score >= 80 ? "B"
: score >= 70 ? "C"
: score >= 60 ? "D"
: "F";
While this works, it becomes very hard to follow for anyone reading the code (including future you). Best practice: If you need more than one level, use if / else if or a function.
// Much clearer
function getGrade(score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
7. Early returns to flatten nesting
Instead of wrapping code in deeper and deeper if blocks, return early when a condition fails. This keeps the "happy path" at the top indentation level.
// BEFORE -- deeply nested
function withdraw(account, amount) {
if (account) {
if (amount > 0) {
if (account.balance >= amount) {
account.balance -= amount;
return { success: true, balance: account.balance };
} else {
return { success: false, error: "Insufficient funds" };
}
} else {
return { success: false, error: "Invalid amount" };
}
} else {
return { success: false, error: "No account" };
}
}
// AFTER -- flat with early returns
function withdraw(account, amount) {
if (!account) return { success: false, error: "No account" };
if (amount <= 0) return { success: false, error: "Invalid amount" };
if (account.balance < amount) return { success: false, error: "Insufficient funds" };
account.balance -= amount;
return { success: true, balance: account.balance };
}
The second version is shorter, easier to scan, and each validation is one line.
8. Guard clauses pattern
A guard clause is an early if + return (or throw) at the top of a function that rejects invalid input immediately. It is the formalization of the early-return technique.
function sendEmail(to, subject, body) {
// Guard clauses
if (!to) throw new Error("Recipient is required");
if (!subject) throw new Error("Subject is required");
if (!body) throw new Error("Body is required");
// Happy path -- all inputs valid
console.log(`Sending email to ${to}: ${subject}`);
}
Benefits:
- Invalid states are handled immediately -- no wondering "where is the error check?"
- The main logic is not indented inside an if block.
- Functions read top-down: validations first, then business logic.
9. Real-world examples
Example 1 -- User authentication check
function authenticateUser(username, password) {
if (!username || !password) {
return { success: false, message: "Username and password are required." };
}
const user = findUserInDatabase(username);
if (!user) {
return { success: false, message: "User not found." };
}
if (user.password !== hashPassword(password)) {
return { success: false, message: "Incorrect password." };
}
if (user.isLocked) {
return { success: false, message: "Account is locked. Contact support." };
}
return { success: true, message: "Welcome back!", user };
}
Example 2 -- Age verification
function checkAge(age) {
if (typeof age !== "number" || isNaN(age)) {
return "Please provide a valid age.";
}
if (age < 0) return "Age cannot be negative.";
if (age < 13) return "You must be at least 13 to sign up.";
if (age < 18) return "Parental consent required.";
return "Welcome! You have full access.";
}
console.log(checkAge(10)); // "You must be at least 13 to sign up."
console.log(checkAge(16)); // "Parental consent required."
console.log(checkAge(25)); // "Welcome! You have full access."
Example 3 -- Form validation
function validateForm(data) {
const errors = [];
if (!data.name || data.name.trim() === "") {
errors.push("Name is required.");
}
if (!data.email || !data.email.includes("@")) {
errors.push("A valid email is required.");
}
if (!data.password) {
errors.push("Password is required.");
} else if (data.password.length < 8) {
errors.push("Password must be at least 8 characters.");
} else if (data.password === data.password.toLowerCase()) {
errors.push("Password must contain at least one uppercase letter.");
}
if (data.age !== undefined) {
if (typeof data.age !== "number" || data.age < 18) {
errors.push("You must be at least 18 years old.");
}
}
return errors.length === 0
? { valid: true }
: { valid: false, errors };
}
const result = validateForm({ name: "Jo", email: "jo@mail.com", password: "abc" });
console.log(result);
// { valid: false, errors: ["Password must be at least 8 characters."] }
10. Common mistakes
| Mistake | Why it fails | Fix |
|---|---|---|
if (x = 5) | Assignment, not comparison -- always truthy for non-zero | Use === |
if (x === 1 || 2) | 2 is always truthy | if (x === 1 || x === 2) |
| Forgetting braces for multi-line blocks | Only the first statement is inside the if | Always use { } |
Checking == null vs === null | == null is true for both null and undefined | Choose intentionally |
// Mistake: forgetting braces
if (loggedIn)
showDashboard();
loadUserData(); // ALWAYS runs -- NOT inside the if!
// Fix
if (loggedIn) {
showDashboard();
loadUserData();
}
Key takeaways
ifevaluates its condition as truthy/falsy -- not just booleans.else ifchains let you handle multiple conditions; order matters (first truthy wins).- Ternary
? :is great for single-expression choices; avoid nesting it. - Guard clauses and early returns eliminate deep nesting and make the happy path obvious.
- Always use
===for comparison and{}braces for clarity.
Explain-It Challenge
Explain without notes:
- What does JavaScript do with the value
""(empty string) when it appears as anifcondition? - Rewrite a nested three-level
ifblock using guard clauses. - When is the ternary operator a bad choice?
Navigation: <-- 1.19 Overview . 1.19.b -- Switch Statement -->