JavaScript Essentials

Control Flow: if, else if, and else

45 min Lesson 7 of 60

What is Control Flow?

Every program you write executes code from top to bottom, line by line. But real applications need to make decisions -- should we show a welcome message or an error? Should we grant access or deny it? Should we apply a discount or charge full price? Control flow is the mechanism that lets your program decide which code to run based on conditions. Without control flow, every program would do the exact same thing every time it runs, which would make software completely useless for real-world tasks.

In JavaScript, the primary tools for control flow are if, else if, and else statements. These are the building blocks of decision-making in your code. Every JavaScript developer uses them hundreds of times a day, so mastering them thoroughly is essential before moving on to more advanced topics.

The if Statement: Your First Decision

The if statement is the simplest form of conditional logic. It evaluates a condition inside parentheses, and if that condition is truthy, it executes the code block inside the curly braces. If the condition is falsy, the code block is skipped entirely.

Example: Basic if Statement Syntax

if (condition) {
    // This code runs ONLY if the condition is truthy
}

// Example with a real condition
let temperature = 35;

if (temperature > 30) {
    console.log('It is hot outside! Stay hydrated.');
}
// Output: "It is hot outside! Stay hydrated."

Let us break down the anatomy of an if statement piece by piece. The keyword if tells JavaScript you are about to make a decision. The parentheses () contain the condition -- any expression that JavaScript will evaluate to a truthy or falsy value. The curly braces {} contain the code block that runs when the condition is truthy. The code inside the block can be one line or hundreds of lines.

Example: if Statement with Different Conditions

let age = 25;
let hasLicense = true;
let accountBalance = 1500;

// Comparing numbers
if (age >= 18) {
    console.log('You are an adult.');
}

// Checking boolean values
if (hasLicense) {
    console.log('You can drive.');
}

// Comparing with a threshold
if (accountBalance > 1000) {
    console.log('You qualify for a premium account.');
}
Note: Always use curly braces {} even when the if block contains only one line. While JavaScript allows you to omit braces for single-line blocks, this is a common source of bugs. When you later add a second line, it will NOT be part of the if block, leading to unexpected behavior. Using braces consistently makes your code safer and more readable.

The else Clause: Handling the Other Case

An if statement on its own only handles one scenario -- when the condition is true. But what about when the condition is false? The else clause provides a fallback code block that runs when the if condition evaluates to falsy. Together, if...else lets you handle both possible outcomes of a condition.

Example: if...else Statement

let userAge = 16;

if (userAge >= 18) {
    console.log('Welcome! You have full access.');
} else {
    console.log('Sorry, you must be 18 or older to access this content.');
}
// Output: "Sorry, you must be 18 or older to access this content."

// Another example: checking if a number is even or odd
let number = 7;

if (number % 2 === 0) {
    console.log(number + ' is even.');
} else {
    console.log(number + ' is odd.');
}
// Output: "7 is odd."

The else clause does not have its own condition -- it catches everything that the if condition does not. Think of it as a safety net. If the if condition is true, the else block is completely ignored. If the if condition is false, the if block is completely ignored and the else block runs instead. Only one of the two blocks will ever execute -- never both.

The else if Clause: Multiple Conditions

Real applications rarely have just two possible outcomes. What if you need to check three, four, or even ten different conditions? The else if clause lets you chain multiple conditions together. JavaScript evaluates each condition in order from top to bottom and executes the first block whose condition is truthy. Once a match is found, all remaining conditions are skipped.

Example: else if Chain

let score = 78;

if (score >= 90) {
    console.log('Grade: A -- Excellent!');
} else if (score >= 80) {
    console.log('Grade: B -- Good job!');
} else if (score >= 70) {
    console.log('Grade: C -- Satisfactory.');
} else if (score >= 60) {
    console.log('Grade: D -- Needs improvement.');
} else {
    console.log('Grade: F -- Failed. Please retake the course.');
}
// Output: "Grade: C -- Satisfactory."

Notice the order matters significantly. If we checked score >= 60 first, a student with a score of 95 would get a "D" because 95 is also greater than or equal to 60. Always order your conditions from most specific (or most restrictive) to least specific. The final else serves as a catch-all for any value that does not match the preceding conditions.

Example: Weather Advisory System

let windSpeed = 45; // in km/h

if (windSpeed >= 120) {
    console.log('HURRICANE WARNING: Seek shelter immediately!');
} else if (windSpeed >= 90) {
    console.log('STORM WARNING: Avoid outdoor activities.');
} else if (windSpeed >= 60) {
    console.log('HIGH WIND ADVISORY: Secure loose objects.');
} else if (windSpeed >= 30) {
    console.log('BREEZY: Conditions are windy but safe.');
} else {
    console.log('CALM: Enjoy the pleasant weather.');
}
// Output: "BREEZY: Conditions are windy but safe."
Tip: You can have as many else if clauses as you need, but if you find yourself writing more than five or six, consider whether a switch statement (covered in the next lesson) or a lookup object would be a cleaner solution. Long chains of else if can become hard to read and maintain.

Nested Conditions: Decisions Inside Decisions

Sometimes a single level of if...else is not enough. You may need to check a condition, and then within that block, check another condition. This is called nesting. While nesting is sometimes necessary, deep nesting (three or more levels) makes code very hard to read and is often a sign that the logic should be restructured.

Example: Nested if Statements

let isLoggedIn = true;
let userRole = 'admin';
let hasPermission = true;

if (isLoggedIn) {
    if (userRole === 'admin') {
        if (hasPermission) {
            console.log('Access granted: Full admin panel.');
        } else {
            console.log('Access denied: Admin permission revoked.');
        }
    } else if (userRole === 'editor') {
        console.log('Access granted: Editor dashboard.');
    } else {
        console.log('Access granted: User dashboard.');
    }
} else {
    console.log('Please log in to continue.');
}
// Output: "Access granted: Full admin panel."

As you can see, nesting three levels deep already makes the code harder to follow. Each level of indentation represents a new decision context, and the reader has to keep track of all the outer conditions to understand when the inner code runs. Later in this lesson, we will learn about guard clauses -- a technique to flatten nested conditions and make your code much more readable.

Warning: Deeply nested conditionals (sometimes called "pyramid of doom" or "arrow code" because of its shape) are one of the most common code readability problems. If you find yourself nesting more than two levels, stop and refactor. Your future self and your teammates will thank you.

Truthy and Falsy Values in Conditions

JavaScript conditions do not require boolean values. Any value can be used as a condition because JavaScript automatically converts (or "coerces") values to booleans when evaluating conditions. Understanding which values are truthy and which are falsy is critical for writing correct conditional logic.

There are exactly eight falsy values in JavaScript. Every other value is truthy.

Example: The Eight Falsy Values

// All of these are FALSY -- the if block will NOT execute
if (false)     { console.log('This will not run'); }
if (0)         { console.log('This will not run'); }
if (-0)        { console.log('This will not run'); }
if (0n)        { console.log('This will not run'); }  // BigInt zero
if ('')        { console.log('This will not run'); }  // empty string
if (null)      { console.log('This will not run'); }
if (undefined) { console.log('This will not run'); }
if (NaN)       { console.log('This will not run'); }

// All of these are TRUTHY -- the if block WILL execute
if (true)       { console.log('Truthy!'); }
if (1)          { console.log('Truthy!'); }
if (-1)         { console.log('Truthy!'); }
if ('hello')    { console.log('Truthy!'); }
if ('0')        { console.log('Truthy! String "0" is not empty'); }
if ('false')    { console.log('Truthy! String "false" is not empty'); }
if ([])         { console.log('Truthy! Empty array is truthy'); }
if ({})         { console.log('Truthy! Empty object is truthy'); }
if (function(){}) { console.log('Truthy! Functions are truthy'); }
Common Gotcha: Empty arrays [] and empty objects {} are truthy! This surprises many beginners. If you want to check if an array is empty, use if (myArray.length === 0). If you want to check if an object has no properties, use if (Object.keys(myObject).length === 0).

Example: Using Truthy/Falsy in Real Code

// Checking if a user provided their name
let userName = '';

if (userName) {
    console.log('Hello, ' + userName + '!');
} else {
    console.log('Hello, Guest!');
}
// Output: "Hello, Guest!" (because empty string is falsy)

// Checking if data was loaded
let userData = null;

if (userData) {
    console.log('User data: ' + userData.name);
} else {
    console.log('No user data available.');
}
// Output: "No user data available." (because null is falsy)

// Checking if a list has items
let notifications = ['New message', 'Update available'];

if (notifications.length) {
    console.log('You have ' + notifications.length + ' notifications.');
} else {
    console.log('No new notifications.');
}
// Output: "You have 2 notifications."

Multiple Conditions with Logical Operators: && and ||

Often you need to check more than one condition at a time. JavaScript provides two main logical operators for combining conditions: the AND operator && and the OR operator ||. These operators let you build complex conditions without nesting multiple if statements.

The AND Operator (&&)

The && operator returns true only when both conditions on either side are truthy. If the first condition is falsy, JavaScript does not even evaluate the second condition (this is called short-circuit evaluation). This is useful for performance and for preventing errors when the second condition depends on the first being true.

Example: AND Operator (&&)

let age = 25;
let hasID = true;

// Both conditions must be true
if (age >= 18 && hasID) {
    console.log('You can enter the venue.');
} else {
    console.log('Entry denied.');
}
// Output: "You can enter the venue."

// Short-circuit evaluation in action
let user = null;

// Without short-circuit, this would throw an error:
// if (user.name === 'Admin') -- ERROR: Cannot read property of null

// With &&, the second part is never evaluated:
if (user && user.name === 'Admin') {
    console.log('Welcome, Admin!');
} else {
    console.log('User not found.');
}
// Output: "User not found." (user is null/falsy, so user.name is never accessed)

The OR Operator (||)

The || operator returns true when at least one condition is truthy. If the first condition is truthy, JavaScript does not evaluate the second condition (short-circuit). This is useful for providing default values or checking multiple acceptable values.

Example: OR Operator (||)

let day = 'Saturday';

if (day === 'Saturday' || day === 'Sunday') {
    console.log('It is the weekend! Time to relax.');
} else {
    console.log('It is a weekday. Time to work.');
}
// Output: "It is the weekend! Time to relax."

// Combining && and || with parentheses for clarity
let userRole = 'editor';
let isActive = true;
let isSuperAdmin = false;

if ((userRole === 'admin' || userRole === 'editor') && isActive) {
    console.log('You can edit content.');
} else if (isSuperAdmin) {
    console.log('Super admin access granted.');
} else {
    console.log('Read-only access.');
}
// Output: "You can edit content."
Tip: When combining && and || in the same condition, always use parentheses to make the grouping explicit. While JavaScript has operator precedence rules (&& is evaluated before ||), relying on precedence without parentheses makes code confusing. Explicit parentheses eliminate ambiguity and make your intent clear to anyone reading the code.

The NOT Operator (!)

The ! operator negates a boolean value -- it turns truthy values to false and falsy values to true. It is commonly used to check if something is NOT true or to flip a condition.

Example: NOT Operator (!)

let isLoggedIn = false;

if (!isLoggedIn) {
    console.log('Please log in to continue.');
}
// Output: "Please log in to continue."

// Double NOT (!!) converts any value to its boolean equivalent
let name = 'Alice';
console.log(!!name);  // true (because "Alice" is truthy)

let emptyName = '';
console.log(!!emptyName);  // false (because "" is falsy)

Guard Clauses and Early Returns

Guard clauses are one of the most powerful techniques for writing clean conditional logic. Instead of nesting conditions deeper and deeper, you check for invalid or edge-case conditions first and return (or exit) early. This keeps the main logic of your function at the top level, making it much easier to read and understand.

Example: Without Guard Clauses (Deeply Nested)

function processOrder(order) {
    if (order) {
        if (order.items.length > 0) {
            if (order.paymentMethod) {
                if (order.shippingAddress) {
                    // Main logic buried deep inside nesting
                    let total = 0;
                    for (let i = 0; i < order.items.length; i++) {
                        total += order.items[i].price;
                    }
                    console.log('Order processed! Total: $' + total);
                    return total;
                } else {
                    console.log('Error: No shipping address.');
                    return null;
                }
            } else {
                console.log('Error: No payment method.');
                return null;
            }
        } else {
            console.log('Error: Cart is empty.');
            return null;
        }
    } else {
        console.log('Error: No order provided.');
        return null;
    }
}

Example: With Guard Clauses (Flat and Clean)

function processOrder(order) {
    // Guard clause 1: Check if order exists
    if (!order) {
        console.log('Error: No order provided.');
        return null;
    }

    // Guard clause 2: Check if cart has items
    if (order.items.length === 0) {
        console.log('Error: Cart is empty.');
        return null;
    }

    // Guard clause 3: Check payment method
    if (!order.paymentMethod) {
        console.log('Error: No payment method.');
        return null;
    }

    // Guard clause 4: Check shipping address
    if (!order.shippingAddress) {
        console.log('Error: No shipping address.');
        return null;
    }

    // Main logic is now at the top level -- easy to read!
    let total = 0;
    for (let i = 0; i < order.items.length; i++) {
        total += order.items[i].price;
    }
    console.log('Order processed! Total: $' + total);
    return total;
}

Both versions do exactly the same thing, but the guard clause version is dramatically easier to read. Each guard clause handles one specific error case and exits early. By the time you reach the main logic, you know that all prerequisites are met. This pattern is used extensively in professional codebases because it reduces cognitive load -- the reader does not have to keep track of nested contexts.

Note: Guard clauses work best inside functions where you can use return to exit early. In loops, you can use continue to skip to the next iteration or break to exit the loop. The core principle is the same -- handle the edge case and get out, so the main logic stays at the top level.

The Ternary Operator: Inline Conditions

The ternary operator is a shorthand for simple if...else statements. It is the only JavaScript operator that takes three operands, which is why it is called "ternary." The syntax is: condition ? valueIfTrue : valueIfFalse. It is best used for simple assignments or return values, not for complex logic with side effects.

Example: Ternary Operator

// Instead of this:
let age = 20;
let status;

if (age >= 18) {
    status = 'adult';
} else {
    status = 'minor';
}

// You can write this:
let status2 = age >= 18 ? 'adult' : 'minor';
console.log(status2); // "adult"

// Ternary in string concatenation
let score = 85;
console.log('Result: ' + (score >= 70 ? 'PASS' : 'FAIL'));
// Output: "Result: PASS"

// Ternary in function arguments
let items = ['apple', 'banana'];
console.log('You have ' + items.length + (items.length === 1 ? ' item' : ' items'));
// Output: "You have 2 items"
Warning: Do not nest ternary operators. While technically valid, nested ternaries like a ? b ? c : d : e ? f : g are extremely difficult to read and understand. If you need multiple conditions, use a proper if...else if...else chain instead. Code readability is always more important than brevity.

Conditional Assignment Patterns

JavaScript offers several patterns for assigning values based on conditions. These are extremely common in real-world code and are worth memorizing.

Example: Default Values with OR (||)

// The || operator returns the first truthy value
let userInput = '';
let displayName = userInput || 'Anonymous';
console.log(displayName); // "Anonymous" (because "" is falsy)

let configPort = 0;
let port = configPort || 3000;
console.log(port); // 3000 (because 0 is falsy -- this is a gotcha!)

// The nullish coalescing operator (??) is safer for defaults
// It only treats null and undefined as "missing"
let configPort2 = 0;
let port2 = configPort2 ?? 3000;
console.log(port2); // 0 (because 0 is not null or undefined)

Example: Conditional Assignment with AND (&&)

// The && operator returns the first falsy value, or the last value if all are truthy
let user = { name: 'Alice', settings: { theme: 'dark' } };

// Safe property access using &&
let theme = user && user.settings && user.settings.theme;
console.log(theme); // "dark"

// Modern alternative: optional chaining (?.)
let theme2 = user?.settings?.theme;
console.log(theme2); // "dark"

// Combining optional chaining with nullish coalescing
let fontSize = user?.settings?.fontSize ?? 16;
console.log(fontSize); // 16 (because fontSize is undefined)

Real-World Example: Form Validation

Form validation is one of the most common uses of conditional logic in web development. Before submitting data to a server, you need to check that the user has provided valid input. Here is a comprehensive example that validates a registration form.

Example: Registration Form Validation

function validateRegistrationForm(formData) {
    let errors = [];

    // Validate username
    if (!formData.username) {
        errors.push('Username is required.');
    } else if (formData.username.length < 3) {
        errors.push('Username must be at least 3 characters long.');
    } else if (formData.username.length > 20) {
        errors.push('Username must be 20 characters or fewer.');
    } else if (!/^[a-zA-Z0-9_]+$/.test(formData.username)) {
        errors.push('Username can only contain letters, numbers, and underscores.');
    }

    // Validate email
    if (!formData.email) {
        errors.push('Email is required.');
    } else if (!formData.email.includes('@')) {
        errors.push('Please enter a valid email address.');
    }

    // Validate password
    if (!formData.password) {
        errors.push('Password is required.');
    } else if (formData.password.length < 8) {
        errors.push('Password must be at least 8 characters long.');
    } else {
        if (!/[A-Z]/.test(formData.password)) {
            errors.push('Password must contain at least one uppercase letter.');
        }
        if (!/[0-9]/.test(formData.password)) {
            errors.push('Password must contain at least one number.');
        }
    }

    // Validate password confirmation
    if (formData.password && formData.password !== formData.confirmPassword) {
        errors.push('Passwords do not match.');
    }

    // Validate age (must agree to terms if under 18)
    if (formData.age && formData.age < 13) {
        errors.push('You must be at least 13 years old to register.');
    } else if (formData.age && formData.age < 18 && !formData.parentalConsent) {
        errors.push('Users under 18 require parental consent.');
    }

    // Return result
    if (errors.length === 0) {
        console.log('Form is valid! Submitting...');
        return { valid: true, errors: [] };
    } else {
        console.log('Form has ' + errors.length + ' error(s):');
        for (let i = 0; i < errors.length; i++) {
            console.log('  - ' + errors[i]);
        }
        return { valid: false, errors: errors };
    }
}

// Testing with invalid data
validateRegistrationForm({
    username: 'ab',
    email: 'invalid',
    password: 'short',
    confirmPassword: 'different',
    age: 10
});

Real-World Example: Permission Checks

Access control is another extremely common use case for conditional logic. Applications need to verify that users have the right permissions before allowing them to perform actions. Here is a permission checking function that demonstrates multiple layers of conditional logic.

Example: Role-Based Permission System

function checkPermission(user, action, resource) {
    // Guard clause: user must exist and be active
    if (!user) {
        return { allowed: false, reason: 'No user provided.' };
    }

    if (!user.isActive) {
        return { allowed: false, reason: 'Account is deactivated.' };
    }

    // Super admins can do anything
    if (user.role === 'superadmin') {
        return { allowed: true, reason: 'Super admin access.' };
    }

    // Check specific permissions based on role and action
    if (action === 'read') {
        // Everyone who is logged in can read public resources
        if (resource.isPublic) {
            return { allowed: true, reason: 'Public resource.' };
        }

        // Admins and editors can read all resources
        if (user.role === 'admin' || user.role === 'editor') {
            return { allowed: true, reason: 'Role-based read access.' };
        }

        // Regular users can only read their own resources
        if (resource.ownerId === user.id) {
            return { allowed: true, reason: 'Owner access.' };
        }

        return { allowed: false, reason: 'You do not have permission to read this resource.' };
    }

    if (action === 'edit') {
        if (user.role === 'admin' || user.role === 'editor') {
            return { allowed: true, reason: 'Role-based edit access.' };
        }

        if (resource.ownerId === user.id) {
            return { allowed: true, reason: 'Owner can edit their own resource.' };
        }

        return { allowed: false, reason: 'You do not have permission to edit this resource.' };
    }

    if (action === 'delete') {
        if (user.role === 'admin') {
            return { allowed: true, reason: 'Admin delete access.' };
        }

        if (resource.ownerId === user.id && !resource.isProtected) {
            return { allowed: true, reason: 'Owner can delete unprotected resources.' };
        }

        return { allowed: false, reason: 'You do not have permission to delete this resource.' };
    }

    return { allowed: false, reason: 'Unknown action: ' + action };
}

// Example usage
let currentUser = { id: 42, role: 'editor', isActive: true };
let article = { id: 101, ownerId: 42, isPublic: true, isProtected: false };

console.log(checkPermission(currentUser, 'edit', article));
// { allowed: true, reason: "Role-based edit access." }

Real-World Example: Age Verification

Age verification is required in many applications -- from content restriction to legal compliance. Here is a robust age verification function that handles various edge cases.

Example: Age Verification System

function verifyAge(birthDateString, requiredAge, currentDate) {
    // Guard clause: validate input
    if (!birthDateString) {
        return { verified: false, message: 'Birth date is required.' };
    }

    let birthDate = new Date(birthDateString);

    // Guard clause: check for valid date
    if (isNaN(birthDate.getTime())) {
        return { verified: false, message: 'Invalid date format.' };
    }

    // Use current date if not provided
    let today = currentDate || new Date();

    // Guard clause: birth date cannot be in the future
    if (birthDate > today) {
        return { verified: false, message: 'Birth date cannot be in the future.' };
    }

    // Calculate age
    let age = today.getFullYear() - birthDate.getFullYear();
    let monthDiff = today.getMonth() - birthDate.getMonth();

    // Adjust age if birthday has not occurred yet this year
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
        age--;
    }

    // Check against required age
    if (age >= requiredAge) {
        return {
            verified: true,
            age: age,
            message: 'Age verified. User is ' + age + ' years old.'
        };
    } else {
        let yearsUntilEligible = requiredAge - age;
        return {
            verified: false,
            age: age,
            message: 'User is ' + age + ' years old. Must be ' + requiredAge +
                     '. Eligible in ' + yearsUntilEligible + ' year(s).'
        };
    }
}

// Testing
console.log(verifyAge('2006-05-15', 18));
console.log(verifyAge('1990-01-01', 21));
console.log(verifyAge('', 18));
console.log(verifyAge('2030-01-01', 18));

Comparison Operators Reference

To write effective conditions, you need to understand all of JavaScript's comparison operators. Here is a complete reference with examples showing common pitfalls.

Example: Comparison Operators

// Strict equality (===) -- checks value AND type (RECOMMENDED)
console.log(5 === 5);       // true
console.log(5 === '5');     // false (different types)
console.log(true === 1);    // false (different types)

// Loose equality (==) -- checks value only, performs type coercion (AVOID)
console.log(5 == '5');      // true (string "5" is coerced to number 5)
console.log(true == 1);     // true (true is coerced to 1)
console.log(null == undefined); // true (special case)
console.log('' == 0);       // true (empty string coerced to 0)

// Strict inequality (!==) -- opposite of ===
console.log(5 !== '5');     // true
console.log(5 !== 5);       // false

// Greater than, Less than, Greater than or equal, Less than or equal
console.log(10 > 5);       // true
console.log(10 < 5);       // false
console.log(10 >= 10);     // true
console.log(10 <= 9);      // false

// String comparison (lexicographic / alphabetical)
console.log('apple' < 'banana');  // true
console.log('A' < 'a');          // true (uppercase letters come first)
console.log('10' < '9');         // true (compared as strings, not numbers!)
Critical Rule: Always use strict equality === and strict inequality !== instead of loose equality == and loose inequality !=. Loose equality performs type coercion, which leads to unexpected and confusing results. The only exception is checking for null or undefined where value == null is a common pattern that checks for both null and undefined in one comparison.

Best Practices for Conditional Logic

Writing effective conditional logic is not just about knowing the syntax -- it is about writing conditions that are clear, maintainable, and bug-free. Here are the most important best practices to follow.

Example: Best Practices

// 1. Use descriptive variable names for conditions
// BAD:
if (u && u.a > 18 && u.r === 'a') { /* ... */ }

// GOOD:
let isAdult = user.age > 18;
let isAdmin = user.role === 'admin';
if (user && isAdult && isAdmin) { /* ... */ }

// 2. Avoid negative conditions when possible
// HARDER TO READ:
if (!isNotLoggedIn) {
    console.log('Welcome!');
}

// EASIER TO READ:
if (isLoggedIn) {
    console.log('Welcome!');
}

// 3. Put the most likely condition first in else if chains
if (status === 'active') {
    // Most common case -- checked first
} else if (status === 'pending') {
    // Second most common
} else if (status === 'suspended') {
    // Rare case
} else {
    // Unknown status
}

// 4. Extract complex conditions into named variables
// HARD TO READ:
if (user.age >= 18 && user.country === 'US' && user.hasVerifiedEmail &&
    (user.subscriptionType === 'premium' || user.trialDaysRemaining > 0)) {
    // ...
}

// MUCH CLEANER:
let isEligibleAge = user.age >= 18;
let isUSBased = user.country === 'US';
let isVerified = user.hasVerifiedEmail;
let hasActiveSubscription = user.subscriptionType === 'premium' ||
                            user.trialDaysRemaining > 0;

if (isEligibleAge && isUSBased && isVerified && hasActiveSubscription) {
    // Now the condition reads like English
}

// 5. Use guard clauses to reduce nesting
// See the detailed section above on guard clauses
Tip: When you write a condition, read it out loud. If it does not make sense as a sentence, refactor it. Good code reads almost like prose. For example: if (isLoggedIn && isAdmin && hasPermission) reads naturally as "if the user is logged in and is an admin and has permission."

Exercise 1: Temperature Converter with Advice

Write a function called temperatureAdvice that takes a temperature in Celsius and returns an object with the temperature in Fahrenheit and appropriate advice. Use if, else if, and else to categorize the temperature: below 0 should say "Freezing! Wear heavy layers and stay indoors if possible." Between 0 and 15 should say "Cold. Wear a warm jacket." Between 16 and 25 should say "Comfortable. Light layers are fine." Between 26 and 35 should say "Warm. Stay hydrated and wear sunscreen." Above 35 should say "Extreme heat! Avoid outdoor activities." Test your function with at least five different temperatures.

Exercise 2: Login System Simulation

Create a function called attemptLogin that takes a username, password, and an array of existing users (each user has username, password, isActive, and failedAttempts properties). The function should: (1) Check if the username exists. (2) If the account is locked (failedAttempts >= 5), return a locked message. (3) If the account is deactivated, return a deactivated message. (4) If the password is wrong, increment the failed attempts counter and warn the user how many attempts remain. (5) If everything is correct, reset failedAttempts to 0 and return a success message. Use guard clauses for a flat structure.

Exercise 3: Ticket Pricing Calculator

Build a function called calculateTicketPrice that takes an object with the following properties: age (number), isStudent (boolean), isMilitary (boolean), dayOfWeek (string), and showTime (string like "14:00" or "20:00"). Base price is $15. Apply the following rules using if/else logic: children under 5 are free, children 5-12 get 50% off, seniors 65+ get 30% off, students get 20% off, military personnel get 25% off (use the highest discount if multiple apply, do not stack them). Matinee shows (before 17:00) get an additional $3 off. Tuesday is discount day -- apply an extra 10% off the final price. Return an object with the original price, discount amount, final price, and a description of all applied discounts.

ES
Edrees Salih
14 hours ago

We are still cooking the magic in the way!