JavaScript Conditionals: if, else, switch, and Ternary Operator Explained
Conditionals let your code make decisions. They are the branching points where your program chooses one path over another based on data. JavaScript gives you several tools for this — if/else, switch, the ternary operator, and modern patterns that can replace them all. Let us cover everything.
The if statement
The most fundamental conditional. If the condition is truthy, the block runs. Otherwise, it is skipped.
// Basic if
const age = 18;
if (age >= 18) {
console.log('You can vote.');
}
// The condition is coerced to boolean
if ('hello') { /* runs — non-empty string is truthy */ }
if (0) { /* skipped — 0 is falsy */ }
if ([]) { /* runs — empty array is truthy! */ }
if (null) { /* skipped — null is falsy */ }
// Single-line if (no braces) — legal but discouraged
if (age >= 18) console.log('Adult');
// Why braces matter:
if (age >= 18)
console.log('You can vote.');
console.log('Welcome!'); // THIS ALWAYS RUNS — it is NOT inside the if!
// Always use braces to avoid this trap
if (age >= 18) {
console.log('You can vote.');
console.log('Welcome!');
}
if…else
const temperature = 35;
if (temperature > 30) {
console.log('It is hot outside.');
} else {
console.log('It is not too hot.');
}
// The else block catches everything not matched by the if
const score = 72;
if (score >= 90) {
console.log('Grade: A');
} else {
console.log('Grade: not A'); // Everything below 90
}
if…else if…else chains
const score = 72;
if (score >= 90) {
console.log('Grade: A');
} else if (score >= 80) {
console.log('Grade: B');
} else if (score >= 70) {
console.log('Grade: C');
} else if (score >= 60) {
console.log('Grade: D');
} else {
console.log('Grade: F');
}
// Important: conditions are checked TOP to BOTTOM
// The FIRST matching condition wins, the rest are skipped
const x = 95;
if (x >= 60) {
console.log('D'); // This prints! Even though x >= 90 too.
} else if (x >= 90) {
console.log('A'); // Never reached because 95 >= 60 matched first.
}
// Always put the most specific conditions first
if (x >= 90) {
console.log('A'); // Correct: checks highest first
} else if (x >= 80) {
console.log('B');
} else if (x >= 70) {
console.log('C');
}
// Multiple conditions with logical operators
const age = 25;
const hasLicense = true;
if (age >= 16 && hasLicense) {
console.log('You can drive.');
} else if (age >= 16 && !hasLicense) {
console.log('You need to get a license.');
} else {
console.log('You are too young to drive.');
}
Nested conditionals vs guard clauses
// BAD: deeply nested conditionals (pyramid of doom)
function processOrder(order) {
if (order) {
if (order.items.length > 0) {
if (order.payment) {
if (order.payment.verified) {
// Finally, the actual logic buried deep inside
return 'Order processed!';
} else {
return 'Payment not verified';
}
} else {
return 'No payment method';
}
} else {
return 'Cart is empty';
}
} else {
return 'No order provided';
}
}
// GOOD: guard clauses (early returns) — flat and readable
function processOrder(order) {
if (!order) return 'No order provided';
if (order.items.length === 0) return 'Cart is empty';
if (!order.payment) return 'No payment method';
if (!order.payment.verified) return 'Payment not verified';
// Happy path — no nesting
return 'Order processed!';
}
// Guard clauses turn nested conditionals into a linear checklist
// Each check eliminates one failure case, and the main logic is at the end
The switch statement
Use switch when you are comparing one value against multiple possible matches. It uses strict equality (===) for comparison.
const day = 'Monday';
switch (day) {
case 'Monday':
console.log('Start of work week');
break;
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
console.log('Midweek');
break;
case 'Friday':
console.log('Almost weekend!');
break;
case 'Saturday':
case 'Sunday':
console.log('Weekend!');
break;
default:
console.log('Invalid day');
}
// CRITICAL: the break statement!
// Without break, execution "falls through" to the next case
const fruit = 'apple';
switch (fruit) {
case 'apple':
console.log('Apple');
// No break! Falls through!
case 'banana':
console.log('Banana');
// No break! Falls through!
case 'cherry':
console.log('Cherry');
break;
}
// Output: 'Apple', 'Banana', 'Cherry' — all three print!
// Intentional fall-through (group cases)
const month = 3;
let season;
switch (month) {
case 12: case 1: case 2:
season = 'Winter';
break;
case 3: case 4: case 5:
season = 'Spring';
break;
case 6: case 7: case 8:
season = 'Summer';
break;
case 9: case 10: case 11:
season = 'Autumn';
break;
}
// Switch with return (in functions, no break needed)
function getEmoji(weather) {
switch (weather) {
case 'sunny': return '☀️';
case 'cloudy': return '☁️';
case 'rainy': return '🌧️';
case 'snowy': return '❄️';
default: return '🌡️';
}
}
// Block scoping in switch cases
// Variables declared with let/const need blocks
switch (action) {
case 'create': {
const item = new Item(); // Scoped to this case block
item.save();
break;
}
case 'delete': {
const item = findItem(); // Different 'item' — no conflict
item.remove();
break;
}
}
The ternary operator
// Syntax: condition ? valueIfTrue : valueIfFalse
const status = age >= 18 ? 'adult' : 'minor';
// The ternary is an EXPRESSION (returns a value)
// if/else is a STATEMENT (does not return a value)
// This means ternary can be used inline:
console.log(\`Status: \${isActive ? 'Active' : 'Inactive'}\`);
const items = [\`\${count} item\${count !== 1 ? 's' : ''}\`];
const className = \`btn \${isPrimary ? 'btn-primary' : 'btn-secondary'}\`;
// Assigning based on condition
const discount = isMember ? 0.2 : 0;
const greeting = hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening';
// Ternary for function arguments
element.classList.toggle('active', isVisible ? true : false);
// Simplify: element.classList.toggle('active', isVisible);
// When NOT to use ternary:
// 1. Side effects (use if/else instead)
// BAD: isValid ? doSomething() : showError();
// GOOD: if (isValid) { doSomething(); } else { showError(); }
// 2. Complex logic (hard to read when nested deeply)
// BAD:
const result = a ? b ? c ? 'd' : 'e' : 'f' : 'g';
// GOOD: use if/else or a function
Object lookup pattern — the switch killer
For many cases where you would use switch, an object lookup is cleaner, faster, and more maintainable.
// Instead of switch:
function getStatusText(code) {
switch (code) {
case 200: return 'OK';
case 201: return 'Created';
case 301: return 'Moved Permanently';
case 400: return 'Bad Request';
case 401: return 'Unauthorized';
case 403: return 'Forbidden';
case 404: return 'Not Found';
case 500: return 'Internal Server Error';
default: return 'Unknown';
}
}
// Use an object lookup:
const STATUS_TEXT = {
200: 'OK',
201: 'Created',
301: 'Moved Permanently',
400: 'Bad Request',
401: 'Unauthorized',
403: 'Forbidden',
404: 'Not Found',
500: 'Internal Server Error',
};
function getStatusText(code) {
return STATUS_TEXT[code] ?? 'Unknown';
}
// This is cleaner, faster, and the mapping is data (can be loaded from config)
// Object lookup with functions (command pattern)
const ACTIONS = {
create: (data) => database.insert(data),
update: (data) => database.update(data),
delete: (data) => database.remove(data),
};
function handleAction(type, data) {
const action = ACTIONS[type];
if (!action) throw new Error(\`Unknown action: \${type}\`);
return action(data);
}
// Map for complex keys (objects, functions, etc.)
const handlers = new Map([
['click', handleClick],
['hover', handleHover],
['scroll', handleScroll],
]);
const handler = handlers.get(eventType);
if (handler) handler(event);
Logical operators as conditionals
// Short-circuit evaluation replaces simple if statements
// Instead of: if (isLoggedIn) { showDashboard(); }
isLoggedIn && showDashboard();
// Instead of: if (!user) { user = 'Anonymous'; }
user = user || 'Anonymous';
// Instead of: if (value === null || value === undefined) { value = 'default'; }
value = value ?? 'default';
// Logical assignment operators (ES2021)
user.name ||= 'Anonymous'; // assign if falsy
user.role ??= 'viewer'; // assign if null/undefined
user.verified &&= checkVerification(); // call if truthy
// Combining conditions
const canAccess = isAdmin || (isUser && hasPermission);
const isValid = name && name.length > 0 && name.length < 100;
// Short-circuit for safe property access (before optional chaining)
const city = user && user.address && user.address.city;
// Modern: const city = user?.address?.city;
Pattern matching with if/else alternatives
// Array.includes for multiple equality checks
// Instead of: if (status === 'active' || status === 'pending' || status === 'trial')
if (['active', 'pending', 'trial'].includes(status)) {
grantAccess();
}
// Object destructuring with defaults
function createUser({ name = 'Anonymous', role = 'user', active = true } = {}) {
// Defaults handle missing values — no if/else needed
return { name, role, active };
}
// Early return pattern for validation
function divide(a, b) {
if (b === 0) return { error: 'Cannot divide by zero' };
return { result: a / b };
}
// Conditional property assignment
const user = {
name: 'Ada',
...(isAdmin && { role: 'admin' }),
...(email && { email }),
};
// If isAdmin is false, role is not added
// If email is empty, email is not added
// Filter + map for conditional array operations
const results = items
.filter(item => item.isActive)
.map(item => item.name);
// No if/else needed — declarative style
Common conditional patterns
// 1. Toggle
let isOpen = false;
isOpen = !isOpen; // true
isOpen = !isOpen; // false
// 2. Clamp to range
function clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
// 3. Default values with validation
function createConfig(options) {
return {
host: options?.host || 'localhost',
port: options?.port ?? 3000, // ?? preserves 0
debug: options?.debug ?? false, // ?? preserves false
timeout: options?.timeout ?? 30000,
};
}
// 4. Type-based dispatch
function format(value) {
if (typeof value === 'string') return \`"\${value}"\`;
if (typeof value === 'number') return value.toFixed(2);
if (typeof value === 'boolean') return value ? 'yes' : 'no';
if (Array.isArray(value)) return value.join(', ');
if (value === null) return 'null';
if (value === undefined) return 'undefined';
return String(value);
}
// 5. Retry logic
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fetch(url);
} catch (error) {
if (attempt === maxRetries) throw error;
await new Promise(r => setTimeout(r, 1000 * attempt));
}
}
}
// 6. Feature flags
const FEATURES = {
darkMode: true,
betaSearch: false,
newDashboard: true,
};
if (FEATURES.darkMode) {
enableDarkMode();
}
// 7. State machines (simple version)
function getNextState(current, action) {
const transitions = {
idle: { START: 'running' },
running: { PAUSE: 'paused', STOP: 'idle' },
paused: { RESUME: 'running', STOP: 'idle' },
};
return transitions[current]?.[action] ?? current;
}
getNextState('idle', 'START') // 'running'
getNextState('running', 'PAUSE') // 'paused'
getNextState('paused', 'STOP') // 'idle'
Best practices for conditionals
// 1. Use positive conditions (easier to read)
// BAD: if (!isNotLoggedIn)
// GOOD: if (isLoggedIn)
// 2. Handle the most common case first
if (status === 'active') {
// 90% of cases
} else if (status === 'suspended') {
// rare case
}
// 3. Extract complex conditions into named variables or functions
// BAD:
if (user.age >= 18 && user.hasVerifiedEmail && !user.isBanned && user.subscription !== 'expired') {
grantAccess();
}
// GOOD:
const isEligible = user.age >= 18
&& user.hasVerifiedEmail
&& !user.isBanned
&& user.subscription !== 'expired';
if (isEligible) {
grantAccess();
}
// 4. Avoid else when you can use early return
// BAD:
function getDiscount(user) {
if (user.isPremium) {
return 0.2;
} else {
return 0;
}
}
// GOOD:
function getDiscount(user) {
if (user.isPremium) return 0.2;
return 0;
}
// 5. Use switch for 3+ equality comparisons against the same value
// Use object lookup for 5+ mappings
// Use if/else for range checks or complex conditions
Conditionals are where your programs come alive — they stop being calculators and start being decision-makers. The key is choosing the right tool: if/else for complex conditions, switch for multiple value matches, ternary for inline expressions, object lookups for data-driven mapping, and guard clauses for clean validation. Write conditions that read like English, and your code will be maintainable for years.