JavaScript conditions convert values to boolean unless an explicit comparison already produces one. Falsy values include false, 0, -0, 0n, an empty string, null, undefined, and NaN; empty arrays and objects are truthy. Use strict equality when comparing values with known types.
A conditional statement refers to a piece of code that does the things based on some condition. When we write code, we will often need to use these conditional statements. There are besically four types of conditional statements-
The if statement is used to execute a block of code only if the specified condition is true.
if(condition) {
statement;
}
if(7 > 5) {
console.log('True');
}
if(condition) {
statement 1;
} else {
statement 2;
}
if(5 > 7) {
console.log('True');
} else {
console.log('False');
}
The if-else-if statement is used to execute a block of code only if the specified condition is true from the several condition.
if(condition) {
statement 1;
} else if {
statement 2;
} else {
statement 3;
}
if(5 > 7) {
console.log('Greater');
} else if (5 == 7) {
console.log('Equals');
} else {
console.log('Smaller');
}
The switch case statement evaluates an expression, and then matching its value to a case clause, once match case is found, it executes statements associated with that case.
switch(expression) {
case value 1:
statement;
break;
case value 2:
statement;
break;
default:
statement;
}
let value = 2;
switch (value) {
case 1:
console.log('Too small');
break;
case 2:
console.log('Exactly!');
break;
case 3:
console.log('Too large');
break;
default:
console.log('Unknown');
}
The ternary operator is a concise one-liner alternative to if-else for simple conditions.
// Syntax: condition ? valueIfTrue : valueIfFalse
const age = 20;
const status = age >= 18 ? 'Adult' : 'Minor';
console.log(status); // Adult
// Nested ternary (use sparingly - can reduce readability)
const score = 75;
const grade = score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 70 ? 'C'
: 'F';
console.log(grade); // C
// Ternary in JSX / template literals
const isLoggedIn = true;
const message = `Welcome, ${isLoggedIn ? 'User' : 'Guest'}!`;
console.log(message); // Welcome, User!
function getGrade(score) {
if (score < 0 || score > 100) {
return 'Invalid score';
} else if (score >= 90) {
return 'A - Excellent';
} else if (score >= 80) {
return 'B - Good';
} else if (score >= 70) {
return 'C - Average';
} else if (score >= 60) {
return 'D - Below Average';
} else {
return 'F - Fail';
}
}
console.log(getGrade(95)); // A - Excellent
console.log(getGrade(72)); // C - Average
console.log(getGrade(45)); // F - Fail
// Same logic with switch on grade band
function getDayName(day) {
switch (day) {
case 1: return 'Monday';
case 2: return 'Tuesday';
case 3: return 'Wednesday';
case 4: return 'Thursday';
case 5: return 'Friday';
case 6: return 'Saturday';
case 7: return 'Sunday';
default: return 'Invalid day';
}
}
console.log(getDayName(5)); // Friday
Use if/else for ranges and compound predicates, switch for one value matched against several strict case values, and a ternary for one small value choice. Avoid nested ternaries and assignment inside conditions because they hide control flow.
The nullish coalescing operator supplies a fallback only for null or undefined, while || also replaces valid falsy values such as 0 and an empty string. Choose based on the data contract, and test boundary and missing-value cases.
An `if` condition converts its expression to boolean. The falsy values are `false`, `0`, `-0`, `0n`, an empty string, `null`, `undefined`, and `NaN`; every object and array is truthy, including an empty one. Write the business condition explicitly when zero, an empty string, or a missing value have different meanings.
Prefer strict equality because it compares without the broad type coercion performed by `==`. Intentional loose equality is rare and needs a clear contract. `Object.is` differs for `NaN` and signed zero, while arrays and objects compare by identity. Two separately created objects with the same properties are not equal references.
The logical operators return operands, not normalized booleans. `a && b` returns the first falsy operand or `b`; `a || b` returns the first truthy operand; `a ?? b` falls back only for `null` or `undefined`. Use `??` when zero, `false`, and an empty string are valid values.
Optional chaining stops a property access or call when its left side is nullish. It does not validate the final type or catch an exception thrown by a method. Combine it with a deliberate default and validation rather than letting missing required data pass silently.
An `if` and `else if` chain selects the first true branch, so order is part of the algorithm. Place narrower conditions before broader overlapping conditions or make the ranges mutually exclusive. A final `else` is useful only when every remaining value has the same meaning; otherwise report or reject the unexpected case.
Guard clauses handle invalid input, missing authorization, unavailable state, or completed work early. They reduce nesting when each guard exits with `return`, `throw`, `continue`, or `break`. Keep validation and side effects separate so a later guard does not run after an earlier branch already modified durable state.
Braces prevent maintenance errors even around a one-line body. Automatic semicolon insertion and misleading indentation can make an unbraced branch look different from its actual scope. Use a formatter and linter, but keep the source structurally clear without relying on color or indentation alone.
The conditional operator is an expression and works well for selecting one compact value. Nested ternaries and branches that perform several side effects are harder to scan and debug. Extract a named decision function or use ordinary statements when the decision needs explanation, logging, or several steps.
A `switch` evaluates its discriminant once and compares case values using strict equality. Execution continues from the matching label until a `break`, `return`, `throw`, or the end, so accidental fall-through is a common bug. Intentional fall-through should group labels visibly or carry a short reason.
Use a switch for a closed set of discrete states such as command type or lifecycle status. Use ranges and compound predicates in `if` statements. For a data-driven mapping from stable keys to values or handlers, an object or `Map` can remove repetitive case syntax, but validate the key and avoid invoking inherited properties accidentally.
A `default` branch should not hide a new unsupported state. In codebases with static analysis, use an exhaustive assertion that fails when a new variant is not handled. In plain JavaScript, validate external input at the boundary and throw or return a structured error for an impossible internal state.
Decision tables help when several independent conditions interact. List each input dimension, valid combination, outcome, and priority before writing branches. The table reveals missing and contradictory rules and can drive parameterized tests without duplicating the implementation logic in the test.
Branch coverage proves that each branch ran, not that every boundary and combination is correct. Test values immediately below, at, and above numeric thresholds; test empty and missing forms separately; and include overlapping rules. Mutation testing can reveal assertions that execute a branch but do not detect an inverted comparison.
When a branch never runs, inspect the actual value and type, the earlier branches that may already match, and any coercion in the predicate. Log named input facts rather than only “condition false.” A debugger conditional breakpoint can stop on the exact combination without adding permanent output to a hot loop.
Keep predicate functions pure where practical. A function such as `canPublish(user, article)` is easier to test than a condition that fetches data, updates counters, and checks authorization at once. Return a reason object when the interface must explain denial, but do not expose sensitive internal policy details to an untrusted caller.
For critical authorization and pricing logic, test denied paths, stale state, missing claims, clock boundaries, and concurrent change. The client can improve user experience, but the trusted server must repeat security decisions. A disabled button is not an access-control branch.
A boolean is sufficient when callers only need yes or no. When they must render a message, choose a recovery action, or create audit evidence, return a structured outcome such as `{ allowed: false, code: "PLAN_LIMIT", retryable: false }`. Keep the stable machine code separate from localized text, and avoid returning internal security details that help an attacker map policy.
Separate validation from authorization and eligibility. Validation asks whether input has the required shape and values. Authorization asks whether the authenticated actor may perform the action on this resource. Eligibility applies business rules such as plan, date, inventory, or workflow state. One broad condition that mixes all three produces vague errors and makes policy changes risky.
Evaluate conditions from trusted, current facts. Client state can be stale or modified, cached permissions may outlive membership changes, and local time can differ from server time. The trusted service should load the authoritative record, bind it to the actor and tenant, evaluate the rule, and commit under a transaction or concurrency check when the state can change between decision and write.
Record which policy version and material facts produced a sensitive decision, while minimizing personal data. This supports incident review without logging secrets or full records. Re-evaluate long-running workflows at the point of irreversible action; approval at screen load does not guarantee the same authorization or inventory remains valid minutes later.
Explore 500+ free tutorials across 20+ languages and frameworks.