Conditional Statements (if, else, switch)

Mastering decision-making in JavaScript programs

Introduction to Conditional Statements

Conditional statements are fundamental building blocks in programming that allow your code to make decisions based on certain conditions. They execute different blocks of code depending on whether a specified condition evaluates to true or false.

In JavaScript, there are several types of conditional statements:

These conditional statements form the foundation of program logic and flow control in JavaScript applications.

flowchart TD A[Conditional Statements] A --> B[if] A --> C[if...else] A --> D[if...else if...else] A --> E[switch] A --> F[Conditional Operator] B --> B1["if (condition) { code }"] C --> C1["if (condition) { code } else { code }"] D --> D1["if (condition) { code } else if (condition) { code } else { code }"] E --> E1["switch (value) { case x: code; break; case y: code; break; default: code; }"] F --> F1["condition ? exprIfTrue : exprIfFalse"]

Analogy: Conditional Statements as Road Intersections

Think of conditional statements like intersections in a road network:

  • An if statement is like a road with a gate that only opens when a condition is met. If the gate is closed, you simply can't proceed down that road.
  • An if...else statement is like a fork in the road where you must choose exactly one path.
  • An if...else if...else statement is like a complex intersection with multiple possible routes, each with its own sign. You follow the first path whose sign says you're eligible.
  • A switch statement is like a roundabout with multiple exits, each clearly labeled with a different destination.
  • The ternary operator is like a quick shortcut that immediately branches in one of two directions based on a single condition.

The if Statement

The most basic conditional statement is the if statement. It executes a block of code only if the specified condition evaluates to true.

Basic Syntax

if (condition) {
    // Code to be executed if the condition is true
}

How it Works

  1. The condition is evaluated
  2. If the condition is truthy, the code inside the block is executed
  3. If the condition is falsy, the code block is skipped

Simple Examples

// Basic if statement
const age = 18;

if (age >= 18) {
    console.log("You are an adult!");
}

// If statement with multiple statements in the block
const score = 85;

if (score >= 70) {
    console.log("You passed the test!");
    console.log(`Your score is ${score}, which is above passing.`);
}

// Remember: Only truthy conditions execute the block
const userName = "";  // Empty string is falsy

if (userName) {
    console.log(`Hello, ${userName}!`);  // This won't execute
}
flowchart TD A[Start] --> B{Condition
is true?} B -->|Yes| C[Execute code block] B -->|No| D[Skip code block] C --> E[Continue program] D --> E

Truthy and Falsy Values

In JavaScript, conditions aren't limited to boolean expressions. Any value can be used as a condition, and JavaScript will automatically convert it to a boolean.

Falsy values (evaluate to false):

All other values are truthy, including:

// Examples of truthy/falsy checks
// These will execute the code block
if (true) { console.log("true is truthy"); }
if (42) { console.log("42 is truthy"); }
if ("hello") { console.log("'hello' is truthy"); }
if ([]) { console.log("Empty array is truthy"); }
if ({}) { console.log("Empty object is truthy"); }
if (function(){}) { console.log("Function is truthy"); }

// These will skip the code block
if (false) { console.log("This won't execute"); }
if (0) { console.log("This won't execute"); }
if ("") { console.log("This won't execute"); }
if (null) { console.log("This won't execute"); }
if (undefined) { console.log("This won't execute"); }
if (NaN) { console.log("This won't execute"); }

Real-World Example: Form Validation

In web forms, we often need to validate user input before proceeding:

function validateUsername(username) {
  // Check if username exists (not empty)
  if (!username) {
    return "Username is required.";
  }
  
  // Check minimum length
  if (username.length < 3) {
    return "Username must be at least 3 characters long.";
  }
  
  // Check if username contains only allowed characters
  if (!/^[a-zA-Z0-9_]+$/.test(username)) {
    return "Username can only contain letters, numbers, and underscores.";
  }
  
  // If we get here, username is valid
  return null; // No error
}

// Usage
const username = "john_doe";
const error = validateUsername(username);

if (error) {
  console.log(`Error: ${error}`);
} else {
  console.log("Username is valid!");
}

The if...else Statement

The if...else statement extends the basic if statement by providing an alternative code block that executes when the condition is false.

Basic Syntax

if (condition) {
    // Code to be executed if the condition is true
} else {
    // Code to be executed if the condition is false
}

How it Works

  1. The condition is evaluated
  2. If the condition is truthy, the first block is executed
  3. If the condition is falsy, the second block (after else) is executed
  4. Exactly one of the blocks will always execute

Simple Examples

// Basic if...else statement
const age = 16;

if (age >= 18) {
    console.log("You can vote!");
} else {
    console.log("You're too young to vote.");
}

// Another example with a function
function isEven(number) {
    if (number % 2 === 0) {
        return "Even";
    } else {
        return "Odd";
    }
}

console.log(isEven(42)); // "Even"
console.log(isEven(17)); // "Odd"
flowchart TD A[Start] --> B{Condition
is true?} B -->|Yes| C[Execute if block] B -->|No| D[Execute else block] C --> E[Continue program] D --> E

Real-World Example: Checkout Process

In an e-commerce application, conditional logic determines the checkout flow:

function processCheckout(cart, userLoggedIn) {
  // Calculate cart total
  const total = cart.items.reduce((sum, item) => {
    return sum + (item.price * item.quantity);
  }, 0);
  
  if (userLoggedIn) {
    // Logged-in user checkout process
    console.log("Retrieving saved shipping information...");
    console.log("Showing payment options including saved methods...");
    
    if (total > 100) {
      console.log("Applying free shipping!");
    } else {
      console.log(`Adding shipping cost of $${calculateShipping(total)}`);
    }
    
    return "Proceed to payment with account discount";
  } else {
    // Guest checkout process
    console.log("Requesting shipping information...");
    console.log("Showing guest payment options...");
    console.log(`Adding shipping cost of $${calculateShipping(total)}`);
    
    return "Proceed to payment as guest";
  }
}

function calculateShipping(total) {
  // Simple shipping calculation
  return total > 50 ? 5.99 : 9.99;
}

// Test with different scenarios
const cart = {
  items: [
    { name: "T-shirt", price: 19.99, quantity: 2 },
    { name: "Jeans", price: 49.99, quantity: 1 }
  ]
};

console.log(processCheckout(cart, true));  // Logged in user
console.log(processCheckout(cart, false)); // Guest user

The if...else if...else Statement

For more complex conditions, you can use the if...else if...else statement to test multiple conditions in sequence.

Basic Syntax

if (condition1) {
    // Code to be executed if condition1 is true
} else if (condition2) {
    // Code to be executed if condition1 is false and condition2 is true
} else if (condition3) {
    // Code to be executed if condition1 and condition2 are false and condition3 is true
} else {
    // Code to be executed if all conditions are false
}

How it Works

  1. Conditions are evaluated in sequence from top to bottom
  2. The first condition that evaluates to true has its associated block executed
  3. Once a condition is found to be true and its block is executed, all other conditions are skipped
  4. The else block is optional and executes only if all conditions are false

Simple Examples

// Grade calculator
const score = 85;
let grade;

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 70) {
    grade = 'C';
} else if (score >= 60) {
    grade = 'D';
} else {
    grade = 'F';
}

console.log(`Your grade is ${grade}`); // "Your grade is B"

// Time-based greeting
const hour = new Date().getHours();
let greeting;

if (hour < 6) {
    greeting = "Good night";
} else if (hour < 12) {
    greeting = "Good morning";
} else if (hour < 18) {
    greeting = "Good afternoon";
} else {
    greeting = "Good evening";
}

console.log(greeting);
flowchart TD A[Start] --> B{Condition1
is true?} B -->|Yes| C[Execute first block] B -->|No| D{Condition2
is true?} D -->|Yes| E[Execute second block] D -->|No| F{Condition3
is true?} F -->|Yes| G[Execute third block] F -->|No| H[Execute else block] C --> I[Continue program] E --> I G --> I H --> I

Analogy: if...else if...else as a Series of Checkpoints

Imagine you're trying to get into a club with multiple checkpoints:

  • First, they check if you're a VIP (if condition1) - if you are, you can skip the line and enter immediately.
  • If you're not a VIP, they check if you have a reservation (else if condition2) - if you do, you can enter through the reservation line.
  • If you don't have a reservation, they check if you're on the guest list (else if condition3) - if you are, you can enter but need to show ID first.
  • If none of these apply, you have to wait in the regular line (else condition).

Just like in this scenario, in JavaScript's if...else if...else statement, once you satisfy one of the conditions, you don't need to check any further conditions - you've found your entry point.

Real-World Example: Shipping Calculator

A shipping calculator that determines cost based on weight, destination, and method:

function calculateShippingCost(weight, destination, method) {
  // Convert weight to kg if in pounds
  const weightInKg = weight.unit === 'lb' ? weight.value * 0.453592 : weight.value;
  
  // Base rate per kg
  let ratePerKg = 0;
  
  // First determine rate based on destination
  if (destination === 'domestic') {
    ratePerKg = 5;
  } else if (destination === 'continental') {
    ratePerKg = 10;
  } else if (destination === 'international') {
    ratePerKg = 20;
  } else {
    throw new Error('Invalid destination');
  }
  
  // Calculate base cost
  let baseCost = weightInKg * ratePerKg;
  
  // Adjust for shipping method
  if (method === 'standard') {
    // Standard shipping - no adjustment needed
  } else if (method === 'express') {
    // Express shipping - 50% more expensive
    baseCost *= 1.5;
  } else if (method === 'overnight') {
    // Overnight shipping - 100% more expensive
    baseCost *= 2;
  } else {
    throw new Error('Invalid shipping method');
  }
  
  // Apply minimum shipping charge
  if (baseCost < 10) {
    baseCost = 10;
  }
  
  // Round to two decimal places and return
  return Math.round(baseCost * 100) / 100;
}

// Test with different scenarios
console.log(calculateShippingCost({ value: 2, unit: 'kg' }, 'domestic', 'standard')); // $10.00
console.log(calculateShippingCost({ value: 5, unit: 'lb' }, 'continental', 'express')); // $34.02
console.log(calculateShippingCost({ value: 1, unit: 'kg' }, 'international', 'overnight')); // $40.00

Nested Conditional Statements

You can place conditional statements inside other conditional statements. This is called "nesting" and allows for more complex decision making.

Basic Syntax

if (condition1) {
    // Outer if block
    if (condition2) {
        // Nested if block
    } else {
        // Nested else block
    }
} else {
    // Outer else block
    if (condition3) {
        // Another nested if block
    }
}

Simple Examples

// Simple nested if example
const userAge = 25;
const hasSubscription = true;

if (userAge >= 18) {
    console.log("User is an adult");
    
    if (hasSubscription) {
        console.log("User has premium access");
    } else {
        console.log("User has standard access");
    }
} else {
    console.log("User is a minor");
    
    if (hasSubscription) {
        console.log("User has juvenile premium access");
    } else {
        console.log("User has restricted access");
    }
}

// Another example with nested conditions
function canDrive(age, hasLicense, country) {
    if (country === 'US') {
        if (age >= 16) {
            if (hasLicense) {
                return "Can drive in the US";
            } else {
                return "Cannot drive without a license";
            }
        } else {
            return "Too young to drive in the US";
        }
    } else if (country === 'UK') {
        if (age >= 17) {
            if (hasLicense) {
                return "Can drive in the UK";
            } else {
                return "Cannot drive without a license";
            }
        } else {
            return "Too young to drive in the UK";
        }
    } else {
        return "Driving laws vary by country";
    }
}
flowchart TD A[Start] --> B{OuterCondition
is true?} B -->|Yes| C{InnerCondition
is true?} C -->|Yes| D[Execute inner if block] C -->|No| E[Execute inner else block] B -->|No| F{AnotherCondition
is true?} F -->|Yes| G[Execute another inner block] F -->|No| H[Skip another inner block] D --> I[Continue program] E --> I G --> I H --> I

Avoiding Deep Nesting

While nesting is powerful, deep nesting can make code hard to read and maintain. Here are techniques to avoid excessive nesting:

1. Early Returns

// Deeply nested version
function processPayment(amount, user) {
    if (amount > 0) {
        if (user.account) {
            if (user.account.balance >= amount) {
                // Process payment
                user.account.balance -= amount;
                return "Payment successful";
            } else {
                return "Insufficient funds";
            }
        } else {
            return "No account found";
        }
    } else {
        return "Invalid payment amount";
    }
}

// Refactored with early returns
function processPaymentBetter(amount, user) {
    // Check invalid conditions first and return early
    if (amount <= 0) {
        return "Invalid payment amount";
    }
    
    if (!user.account) {
        return "No account found";
    }
    
    if (user.account.balance < amount) {
        return "Insufficient funds";
    }
    
    // Process payment only if all conditions are met
    user.account.balance -= amount;
    return "Payment successful";
}

2. Logical Operators

// Nested ifs
if (isLoggedIn) {
    if (hasPermission) {
        if (resourceExists) {
            accessResource();
        }
    }
}

// Using logical operators
if (isLoggedIn && hasPermission && resourceExists) {
    accessResource();
}

3. Extracting Functions

// Nested conditions
function processOrder(order) {
    if (order.items.length > 0) {
        if (order.paymentMethod) {
            if (order.shippingAddress) {
                // Process order...
            } else {
                return "Shipping address required";
            }
        } else {
            return "Payment method required";
        }
    } else {
        return "Cart is empty";
    }
}

// Refactored with validation function
function processOrderBetter(order) {
    const validationError = validateOrder(order);
    if (validationError) {
        return validationError;
    }
    
    // Process order...
}

function validateOrder(order) {
    if (order.items.length === 0) {
        return "Cart is empty";
    }
    
    if (!order.paymentMethod) {
        return "Payment method required";
    }
    
    if (!order.shippingAddress) {
        return "Shipping address required";
    }
    
    return null; // No errors
}

Real-World Example: User Authentication Flow

Authentication systems often require multiple levels of validation:

function authenticateUser(credentials) {
  // First check if required fields exist
  if (!credentials.username || !credentials.password) {
    return {
      success: false,
      message: "Username and password are required"
    };
  }
  
  // Find user in database (simulated here)
  const user = findUserByUsername(credentials.username);
  
  if (!user) {
    // Don't reveal if username exists for security
    return {
      success: false,
      message: "Invalid username or password"
    };
  }
  
  // Check if account is locked
  if (user.accountLocked) {
    return {
      success: false,
      message: "Account is locked. Please contact support."
    };
  }
  
  // Verify password (simulated)
  const passwordMatches = verifyPassword(credentials.password, user.passwordHash);
  
  if (!passwordMatches) {
    // Increment failed login attempts (simulated)
    incrementFailedLogins(user.id);
    
    // Check if we need to lock account
    if (user.failedLoginAttempts + 1 >= 5) {
      lockAccount(user.id);
      return {
        success: false,
        message: "Too many failed attempts. Account has been locked."
      };
    }
    
    return {
      success: false,
      message: "Invalid username or password"
    };
  }
  
  // Check if two-factor auth is enabled
  if (user.twoFactorEnabled) {
    // Generate and send 2FA code (simulated)
    const twoFactorCode = generateTwoFactorCode(user.id);
    sendTwoFactorCode(user.phone, twoFactorCode);
    
    return {
      success: false,
      requiresTwoFactor: true,
      message: "Please enter the verification code sent to your phone"
    };
  }
  
  // Authentication successful
  // Reset failed attempts and generate session token
  resetFailedLogins(user.id);
  const token = generateSessionToken(user.id);
  
  return {
    success: true,
    token: token,
    user: {
      id: user.id,
      username: user.username,
      name: user.name,
      email: user.email
    }
  };
}

// Simulated helper functions
function findUserByUsername(username) {
  // Simulate database lookup
  const users = {
    'john_doe': {
      id: 123,
      username: 'john_doe',
      name: 'John Doe',
      email: 'john@example.com',
      passwordHash: 'hashed_password',
      twoFactorEnabled: true,
      phone: '+1234567890',
      accountLocked: false,
      failedLoginAttempts: 0
    }
  };
  
  return users[username];
}

function verifyPassword(password, hash) {
  // In real apps, you'd verify the password against the hash
  return password === 'correct_password';
}

function incrementFailedLogins(userId) {
  console.log(`Incrementing failed logins for user ${userId}`);
  // Would update database in real app
}

function lockAccount(userId) {
  console.log(`Locking account for user ${userId}`);
  // Would update database in real app
}

function resetFailedLogins(userId) {
  console.log(`Resetting failed logins for user ${userId}`);
  // Would update database in real app
}

function generateTwoFactorCode(userId) {
  // Would generate a random code in real app
  return '123456';
}

function sendTwoFactorCode(phone, code) {
  console.log(`Sending code ${code} to ${phone}`);
  // Would send SMS in real app
}

function generateSessionToken(userId) {
  // Would generate a secure token in real app
  return 'secure_token_123';
}

// Test authentication
console.log(authenticateUser({
  username: 'john_doe',
  password: 'wrong_password'
}));

console.log(authenticateUser({
  username: 'john_doe',
  password: 'correct_password'
}));

The switch Statement

The switch statement provides a way to handle multiple conditions against a single value. It can be more readable than multiple if...else if statements when comparing a value against many possible cases.

Basic Syntax

switch (expression) {
    case value1:
        // Code to execute if expression === value1
        break;
    case value2:
        // Code to execute if expression === value2
        break;
    case value3:
        // Code to execute if expression === value3
        break;
    default:
        // Code to execute if none of the cases match
        break;
}

How it Works

  1. The expression is evaluated once
  2. The resulting value is compared with the values of each case
  3. If there's a match, the block of code associated with that case is executed
  4. The break statement exits the switch block
  5. If no match is found, the default block (if present) is executed
  6. Without break, execution "falls through" to the next case

Simple Examples

// Switch based on a string value
const day = new Date().getDay();
let dayName;

switch (day) {
    case 0:
        dayName = "Sunday";
        break;
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    case 4:
        dayName = "Thursday";
        break;
    case 5:
        dayName = "Friday";
        break;
    case 6:
        dayName = "Saturday";
        break;
    default:
        dayName = "Invalid day";
        break;
}

console.log(`Today is ${dayName}`);

// Equivalent to if...else if...else
function getDayName(day) {
    if (day === 0) {
        return "Sunday";
    } else if (day === 1) {
        return "Monday";
    } else if (day === 2) {
        return "Tuesday";
    } // and so on...
}
flowchart TD A[Start] --> B[Evaluate expression] B --> C{Match case 1?} C -->|Yes| D[Execute case 1 code] C -->|No| E{Match case 2?} E -->|Yes| F[Execute case 2 code] E -->|No| G{Match case 3?} G -->|Yes| H[Execute case 3 code] G -->|No| I[Execute default code] D --> J[break statement?] F --> K[break statement?] H --> L[break statement?] I --> M[Continue program] J -->|Yes| M J -->|No| F K -->|Yes| M K -->|No| H L -->|Yes| M L -->|No| I

Fall-Through Behavior

Without a break statement, execution will "fall through" to the next case, which can be useful in some scenarios:

// Using fall-through to handle multiple cases the same way
const month = new Date().getMonth();
let season;

switch (month) {
    case 11: // December
    case 0:  // January
    case 1:  // February
        season = "Winter";
        break;
    case 2:  // March
    case 3:  // April
    case 4:  // May
        season = "Spring";
        break;
    case 5:  // June
    case 6:  // July
    case 7:  // August
        season = "Summer";
        break;
    case 8:  // September
    case 9:  // October
    case 10: // November
        season = "Fall";
        break;
    default:
        season = "Invalid month";
        break;
}

console.log(`The current season is ${season}`);

Switch vs. if...else if

When to use switch instead of if...else if:

When to use if...else if instead of switch:

Real-World Example: Command Processor

A simple command processor might use a switch statement to handle different commands:

function processCommand(command, args) {
  // Parse command and arguments
  command = command.toLowerCase().trim();
  
  switch (command) {
    case 'help':
      displayHelpMenu();
      break;
      
    case 'create':
      if (!args || args.length === 0) {
        console.log("Error: 'create' command requires a name parameter");
        console.log("Usage: create [name]");
      } else {
        createItem(args[0]);
      }
      break;
      
    case 'list':
      // Fall-through for aliases
    case 'ls':
    case 'show':
      listItems(args);
      break;
      
    case 'delete':
    case 'remove':
      if (!args || args.length === 0) {
        console.log("Error: 'delete' command requires an id parameter");
        console.log("Usage: delete [id]");
      } else {
        deleteItem(args[0]);
      }
      break;
      
    case 'edit':
    case 'update':
      if (!args || args.length < 2) {
        console.log("Error: 'edit' command requires id and new value parameters");
        console.log("Usage: edit [id] [new value]");
      } else {
        updateItem(args[0], args[1]);
      }
      break;
      
    case 'exit':
    case 'quit':
      console.log("Exiting program...");
      return false; // Signal to exit program
      
    default:
      console.log(`Unknown command: ${command}`);
      console.log("Type 'help' to see available commands");
      break;
  }
  
  return true; // Continue program execution
}

// Mock implementation of command functions
function displayHelpMenu() {
  console.log("Available commands:");
  console.log("  help - Display this help menu");
  console.log("  create [name] - Create a new item");
  console.log("  list - List all items");
  console.log("  delete [id] - Delete an item by id");
  console.log("  edit [id] [value] - Update an item");
  console.log("  exit - Exit the program");
}

function createItem(name) {
  console.log(`Creating item: ${name}`);
}

function listItems(args) {
  console.log("Listing items:", args ? args.join(', ') : 'all');
}

function deleteItem(id) {
  console.log(`Deleting item with id: ${id}`);
}

function updateItem(id, newValue) {
  console.log(`Updating item ${id} to: ${newValue}`);
}

// Test the command processor
let running = true;
const testCommands = [
  { cmd: 'help', args: [] },
  { cmd: 'create', args: ['task1'] },
  { cmd: 'ls', args: [] },
  { cmd: 'edit', args: ['1', 'updated task'] },
  { cmd: 'invalid', args: [] },
  { cmd: 'exit', args: [] }
];

for (const test of testCommands) {
  console.log(`\n> ${test.cmd} ${test.args.join(' ')}`);
  running = processCommand(test.cmd, test.args);
  
  if (!running) {
    console.log("Program terminated");
    break;
  }
}

The Conditional (Ternary) Operator

The conditional (ternary) operator is a shorthand way to write simple if...else statements. It takes three operands: a condition, an expression to execute if the condition is truthy, and an expression to execute if the condition is falsy.

Basic Syntax

condition ? expressionIfTrue : expressionIfFalse

How it Works

  1. The condition is evaluated
  2. If the condition is truthy, the expressionIfTrue is evaluated and becomes the result
  3. If the condition is falsy, the expressionIfFalse is evaluated and becomes the result

Simple Examples

// Basic ternary operator
const age = 20;
const status = age >= 18 ? "Adult" : "Minor";
console.log(status); // "Adult"

// Equivalent if...else statement
let status2;
if (age >= 18) {
    status2 = "Adult";
} else {
    status2 = "Minor";
}

// Using with template literals
const greeting = `Good ${new Date().getHours() < 12 ? "morning" : "afternoon"}`;
console.log(greeting);

// Returning values in functions
function getAbsoluteValue(number) {
    return number >= 0 ? number : -number;
}
console.log(getAbsoluteValue(-5)); // 5

Nested Ternaries

You can nest ternary operators, but this can quickly become hard to read:

// Simple nested ternary
const score = 85;
const grade = score >= 90 ? 'A' : 
              score >= 80 ? 'B' : 
              score >= 70 ? 'C' : 
              score >= 60 ? 'D' : 'F';
console.log(grade); // 'B'

// Improved readability with formatting
const grade2 = score >= 90 ? 'A' 
             : score >= 80 ? 'B' 
             : score >= 70 ? 'C' 
             : score >= 60 ? 'D' 
             : 'F';

// Often more readable with if...else for complex conditions
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';
}

Best Practices

Real-World Example: UI Component

Ternary operators are very common in front-end frameworks for conditional rendering:

// Simple button component with status-based styling
function renderButton(text, isEnabled, isLoading) {
  // Determine class based on state
  const className = isLoading ? 'btn-loading' :
                   !isEnabled ? 'btn-disabled' :
                   'btn-enabled';
  
  // Determine button text based on state
  const buttonText = isLoading ? 'Processing...' : text;
  
  // Determine icon based on state
  const icon = isLoading ? 'spinner' :
               !isEnabled ? 'lock' :
               'check';
  
  // Create button HTML
  return `
    <button class="btn ${className}" ${!isEnabled && !isLoading ? 'disabled' : ''}>
      ${isLoading ? '<span class="loading-spinner"></span>' : ''}
      <i class="icon-${icon}"></i>
      ${buttonText}
    </button>
  `;
}

// Test button component in different states
console.log(renderButton('Submit', true, false));  // Enabled
console.log(renderButton('Submit', false, false)); // Disabled
console.log(renderButton('Submit', true, true));   // Loading

Best Practices for Conditional Statements

1. Keep Conditions Simple

// Avoid complex conditions
if (user.age >= 18 && user.country === 'US' && user.verifiedEmail && !user.isBlocked) {
    // Allow access
}

// Better - extract into meaningful functions or variables
const isAdult = user.age >= 18;
const isFromUS = user.country === 'US';
const hasVerifiedEmail = user.verifiedEmail;
const isNotBlocked = !user.isBlocked;

if (isAdult && isFromUS && hasVerifiedEmail && isNotBlocked) {
    // Allow access
}

2. Watch Out for Potential Errors

// Risky - might cause "Cannot read property 'name' of undefined"
if (user.profile.name === 'Admin') {
    // Grant admin access
}

// Safer - check for existence first
if (user && user.profile && user.profile.name === 'Admin') {
    // Grant admin access
}

// Even better with optional chaining (ES2020)
if (user?.profile?.name === 'Admin') {
    // Grant admin access
}

3. Use Guard Clauses

// Without guard clauses
function processOrder(order) {
    if (order) {
        if (order.items.length > 0) {
            if (order.paymentVerified) {
                // Process the order
                return "Order processed successfully";
            } else {
                return "Payment not verified";
            }
        } else {
            return "No items in order";
        }
    } else {
        return "Invalid order";
    }
}

// With guard clauses
function processOrderBetter(order) {
    if (!order) {
        return "Invalid order";
    }
    
    if (order.items.length === 0) {
        return "No items in order";
    }
    
    if (!order.paymentVerified) {
        return "Payment not verified";
    }
    
    // Process the order
    return "Order processed successfully";
}

4. Avoid Assignments in Conditions

// Bad practice - easy to confuse with equality check
if (user = getUser()) {
    // Do something with user
}

// Better practice
const user = getUser();
if (user) {
    // Do something with user
}

5. Be Careful with Implicit Type Conversions

// Risky - relies on type coercion
if (userId == 123) {
    // User with id 123
}

// Better - explicit comparison
if (userId === 123) {
    // User with id 123
}

6. Avoid Nested Conditionals

// Instead of nested if statements
if (condition1) {
    if (condition2) {
        if (condition3) {
            // Do something
        }
    }
}

// Use logical operators
if (condition1 && condition2 && condition3) {
    // Do something
}

7. Consider Using Objects or Maps Instead of switch

// Traditional switch statement
function getColor(fruit) {
    switch (fruit.toLowerCase()) {
        case 'apple':
            return 'red';
        case 'banana':
            return 'yellow';
        case 'grape':
            return 'purple';
        default:
            return 'unknown';
    }
}

// Object lookup approach
function getColorBetter(fruit) {
    const fruitColors = {
        apple: 'red',
        banana: 'yellow',
        grape: 'purple'
    };
    
    return fruitColors[fruit.toLowerCase()] || 'unknown';
}

Real-World Example: Authorization System

A well-structured authorization system using conditional best practices:

// User role-based authorization system
class AuthorizationService {
  constructor() {
    // Permission definitions
    this.permissions = {
      'create:article': ['admin', 'editor', 'author'],
      'edit:article': ['admin', 'editor', 'author'],
      'delete:article': ['admin', 'editor'],
      'publish:article': ['admin', 'editor'],
      
      'create:comment': ['admin', 'editor', 'author', 'user'],
      'edit:comment': ['admin', 'editor', 'author', 'user'],
      'delete:comment': ['admin', 'editor', 'moderator'],
      
      'manage:users': ['admin'],
      'view:analytics': ['admin', 'editor']
    };
    
    // Role hierarchy
    this.roleHierarchy = {
      'admin': 100,
      'editor': 80,
      'moderator': 60,
      'author': 40,
      'user': 20,
      'guest': 0
    };
  }
  
  // Check if user has a specific permission
  hasPermission(user, permission) {
    // Guard clause for invalid inputs
    if (!user || !permission) {
      return false;
    }
    
    // Admin has all permissions
    if (user.role === 'admin') {
      return true;
    }
    
    // Check if permission exists
    if (!this.permissions[permission]) {
      return false;
    }
    
    // Check if user's role is in the permitted roles list
    return this.permissions[permission].includes(user.role);
  }
  
  // Check if user can perform action on a resource
  canAccess(user, action, resource, ownerId) {
    // Guard clause - no user means guest access only
    if (!user) {
      user = { role: 'guest' };
    }
    
    // Guard clause - if resource doesn't exist, no one can access
    if (!resource) {
      return false;
    }
    
    // Check permission based on action and resource type
    const permission = `${action}:${resource}`;
    const hasBasePermission = this.hasPermission(user, permission);
    
    // If no base permission, deny access
    if (!hasBasePermission) {
      return false;
    }
    
    // Special case: users can only edit their own content unless they have elevated roles
    if (action === 'edit' && ownerId && user.id !== ownerId) {
      // Only allow if user role is higher than author
      const userLevel = this.roleHierarchy[user.role] || 0;
      return userLevel >= this.roleHierarchy['editor'];
    }
    
    // Special case: only admins can delete content from other admins
    if (action === 'delete' && ownerId) {
      const ownerRole = this.getUserRole(ownerId);
      if (ownerRole === 'admin' && user.role !== 'admin') {
        return false;
      }
    }
    
    // Base permission check passed
    return true;
  }
  
  // Mock method to get a user's role
  getUserRole(userId) {
    // In a real app, this would look up the user in a database
    const mockUsers = {
      1: 'admin',
      2: 'editor',
      3: 'author',
      4: 'user'
    };
    
    return mockUsers[userId] || 'guest';
  }
}

// Example usage
const auth = new AuthorizationService();

// Test users
const adminUser = { id: 1, role: 'admin' };
const editorUser = { id: 2, role: 'editor' };
const authorUser = { id: 3, role: 'author' };
const regularUser = { id: 4, role: 'user' };

// Test permissions
console.log("Can admin delete article?", auth.hasPermission(adminUser, 'delete:article')); // true
console.log("Can author delete article?", auth.hasPermission(authorUser, 'delete:article')); // false
console.log("Can user create comment?", auth.hasPermission(regularUser, 'create:comment')); // true

// Test access control
console.log("Can author edit own article?", 
  auth.canAccess(authorUser, 'edit', 'article', 3)); // true
  
console.log("Can author edit other's article?", 
  auth.canAccess(authorUser, 'edit', 'article', 4)); // false
  
console.log("Can editor edit other's article?", 
  auth.canAccess(editorUser, 'edit', 'article', 3)); // true

Practical Exercise

Let's practice what we've learned with some exercises about conditional statements.

Exercise 1: Traffic Light Controller

Create a traffic light controller function that determines the next light color:

// Traffic light controller
function getNextLight(currentLight) {
  // Convert to lowercase for consistency
  currentLight = (currentLight || '').toLowerCase();
  
  // Determine next light
  switch (currentLight) {
    case 'green':
      return 'yellow';
    case 'yellow':
      return 'red';
    case 'red':
      return 'green';
    default:
      return 'Invalid light color';
  }
}

// Test cases
console.log(getNextLight('green'));  // yellow
console.log(getNextLight('yellow')); // red
console.log(getNextLight('red'));    // green
console.log(getNextLight('YELLOW')); // red (case insensitive)
console.log(getNextLight('blue'));   // Invalid light color

// Advanced version with pedestrian crossing
function advancedTrafficLight(currentLight, pedestrianWaiting = false) {
  // Convert to lowercase for consistency
  currentLight = (currentLight || '').toLowerCase();
  
  if (currentLight === 'green') {
    // If pedestrian is waiting, change to yellow
    return pedestrianWaiting ? 'yellow' : 'green';
  } else if (currentLight === 'yellow') {
    return 'red';
  } else if (currentLight === 'red') {
    // Always change red to green
    return 'green';
  } else {
    return 'Invalid light color';
  }
}

// Test advanced traffic light
console.log(advancedTrafficLight('green', false)); // green (stays green)
console.log(advancedTrafficLight('green', true));  // yellow (pedestrian waiting)
console.log(advancedTrafficLight('yellow'));       // red

Exercise 2: Enhanced Permission System

Create a function that determines if a user can access a specific resource:

// User access control system
function canAccessResource(user, resource) {
  // Guard clauses - if user or resource don't exist, deny access
  if (!user || !resource) {
    return false;
  }
  
  // Check if resource is public - anyone can access
  if (resource.accessLevel === 'public') {
    return true;
  }
  
  // If not logged in, deny access to non-public resources
  if (!user.isLoggedIn) {
    return false;
  }
  
  // Check membership level required resources
  if (resource.accessLevel === 'member' && user.isMember) {
    return true;
  }
  
  // Check premium resources
  if (resource.accessLevel === 'premium' && user.isPremium) {
    return true;
  }
  
  // Check if user is the owner of the resource
  if (resource.ownerId === user.id) {
    return true;
  }
  
  // Check if user is an admin (can access everything)
  if (user.isAdmin) {
    return true;
  }
  
  // If none of the above conditions are met, deny access
  return false;
}

// Test users
const guestUser = {
  isLoggedIn: false
};

const regularUser = {
  id: 123,
  isLoggedIn: true,
  isMember: true,
  isPremium: false,
  isAdmin: false
};

const premiumUser = {
  id: 456,
  isLoggedIn: true,
  isMember: true,
  isPremium: true,
  isAdmin: false
};

const adminUser = {
  id: 789,
  isLoggedIn: true,
  isMember: true,
  isPremium: true,
  isAdmin: true
};

// Test resources
const resources = [
  { id: 1, name: 'Public Article', accessLevel: 'public', ownerId: 123 },
  { id: 2, name: 'Member Tutorial', accessLevel: 'member', ownerId: 456 },
  { id: 3, name: 'Premium Course', accessLevel: 'premium', ownerId: 789 },
  { id: 4, name: 'Regular User Content', accessLevel: 'member', ownerId: 123 }
];

// Test access control
console.log('Guest access to public article:', 
  canAccessResource(guestUser, resources[0])); // true
  
console.log('Guest access to member tutorial:', 
  canAccessResource(guestUser, resources[1])); // false
  
console.log('Regular user access to premium course:', 
  canAccessResource(regularUser, resources[2])); // false
  
console.log('Regular user access to own content:', 
  canAccessResource(regularUser, resources[3])); // true
  
console.log('Premium user access to premium course:', 
  canAccessResource(premiumUser, resources[2])); // true
  
console.log('Admin access to any content:', 
  canAccessResource(adminUser, resources[2])); // true

Exercise 3: Shopping Cart Discount Calculator

Create a function that calculates discounts based on various conditions:

// Shopping cart discount calculator
function calculateTotalWithDiscount(cart, couponCode, userType) {
  // Guard clause for empty cart
  if (!cart || !cart.items || cart.items.length === 0) {
    return {
      subtotal: 0,
      discount: 0,
      total: 0,
      message: "Cart is empty"
    };
  }
  
  // Calculate subtotal
  let subtotal = 0;
  for (const item of cart.items) {
    subtotal += item.price * item.quantity;
  }
  
  // Initialize discount variables
  let discountAmount = 0;
  let discountMessage = null;
  
  // Check for product-specific discounts
  cart.items.forEach(item => {
    // Buy one get one free for eligible products
    if (item.eligibleForBOGO && item.quantity >= 2) {
      const freeItems = Math.floor(item.quantity / 2);
      const itemDiscount = freeItems * item.price;
      discountAmount += itemDiscount;
      discountMessage = discountMessage || `BOGO applied to ${item.name}`;
    }
    
    // Clearance items get additional 10% off
    if (item.clearance) {
      const clearanceDiscount = item.price * item.quantity * 0.10;
      discountAmount += clearanceDiscount;
      discountMessage = discountMessage 
        ? `${discountMessage}, Clearance discount applied`
        : 'Clearance discount applied';
    }
  });
  
  // Apply coupon codes
  if (couponCode) {
    switch (couponCode.toUpperCase()) {
      case 'SAVE10':
        // 10% off entire order
        const coupon10Discount = subtotal * 0.10;
        if (coupon10Discount > discountAmount) {
          discountAmount = coupon10Discount;
          discountMessage = '10% off coupon applied';
        }
        break;
        
      case 'SAVE20':
        // 20% off orders over $100
        if (subtotal >= 100) {
          const coupon20Discount = subtotal * 0.20;
          if (coupon20Discount > discountAmount) {
            discountAmount = coupon20Discount;
            discountMessage = '20% off coupon applied';
          }
        } else {
          discountMessage = 'SAVE20 requires $100 minimum purchase';
        }
        break;
        
      case 'FREESHIP':
        // Free shipping handled elsewhere
        discountMessage = discountMessage 
          ? `${discountMessage}, Free shipping applied`
          : 'Free shipping applied';
        break;
        
      default:
        discountMessage = discountMessage 
          ? `${discountMessage}, Invalid coupon code`
          : 'Invalid coupon code';
    }
  }
  
  // Apply user type discounts
  if (userType) {
    let userDiscount = 0;
    
    if (userType === 'premium') {
      // Premium members get 15% off
      userDiscount = subtotal * 0.15;
    } else if (userType === 'member') {
      // Regular members get 5% off
      userDiscount = subtotal * 0.05;
    } else if (userType === 'employee') {
      // Employees get 30% off
      userDiscount = subtotal * 0.30;
    }
    
    // Use the better discount
    if (userDiscount > discountAmount) {
      discountAmount = userDiscount;
      discountMessage = `${userType} discount applied`;
    }
  }
  
  // Calculate final total
  const total = Math.max(0, subtotal - discountAmount);
  
  // Round to two decimal places
  return {
    subtotal: parseFloat(subtotal.toFixed(2)),
    discount: parseFloat(discountAmount.toFixed(2)),
    total: parseFloat(total.toFixed(2)),
    message: discountMessage || "No discounts applied"
  };
}

// Test the discount calculator
const cart = {
  items: [
    { 
      name: "T-shirt", 
      price: 19.99, 
      quantity: 2,
      eligibleForBOGO: true,
      clearance: false
    },
    { 
      name: "Jeans", 
      price: 49.99, 
      quantity: 1,
      eligibleForBOGO: false,
      clearance: false
    },
    { 
      name: "Clearance Socks", 
      price: 4.99, 
      quantity: 3,
      eligibleForBOGO: false,
      clearance: true
    }
  ]
};

console.log(calculateTotalWithDiscount(cart)); // No discounts except built-in
console.log(calculateTotalWithDiscount(cart, 'SAVE10')); // 10% coupon
console.log(calculateTotalWithDiscount(cart, 'SAVE20')); // 20% coupon (over $100)
console.log(calculateTotalWithDiscount(cart, 'INVALID')); // Invalid coupon
console.log(calculateTotalWithDiscount(cart, 'SAVE10', 'premium')); // Premium member

Key Takeaways

Additional Resources