Skip to content

Conditionals

docs.scrimba.com

A program that always does the same thing is not much use. You want a page that shows one message to a signed-in visitor and another to a stranger, a form that accepts an age of 18 but not 12, a game that reports whether you won or lost. All of that needs a way to ask a question and act on the answer. That is what conditionals do: they let your code choose what to run based on whether something is true.

You already have the pieces. The comparison and logical operators produce the true-or-false answers, and this chapter is about acting on them.

Running code only when a condition is true

An if statement runs a block of code only when a condition is true. You write the word if, a condition in parentheses, and a block in curly braces. If the condition is true, the block runs. If it is not, JavaScript skips the block and carries on.

js
const age = 20;

if (age >= 18) {
  console.log("You can enter."); // You can enter.
}

The condition age >= 18 is either true or false. Here age is 20, so it is true, and the message prints. Change age to 12 and the condition is false, so the block is skipped and nothing prints.

Often you want a fallback for when the condition is false. That is what else is for:

js
const age = 12;

if (age >= 18) {
  console.log("You can enter.");
} else {
  console.log("Come back when you are older."); // Come back when you are older.
}

Exactly one of the two blocks runs, never both. When you have more than two outcomes, else if lets you check another condition:

js
const score = 74;

if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 70) {
  console.log("Grade: B"); // Grade: B
} else {
  console.log("Grade: C");
}

JavaScript checks each condition from the top and stops at the first one that is true. A score of 74 is not >= 90, but it is >= 70, so it prints Grade: B and never looks at the else.

An if statement runs a block when its condition is true, and an optional else block runs when it is not. Chaining with else if lets you check several conditions in order.

js
const score = 74;

if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 70) {
  console.log("Grade: B"); // Grade: B
} else if (score >= 50) {
  console.log("Grade: C");
} else {
  console.log("Grade: F");
}

The order matters, and it is the part people get wrong. JavaScript tests each condition from the top and runs the block for the first true one, then skips the rest. That is why the ranges go from high to low here: once score >= 70 is reached, a 74 matches and the chain stops. If you wrote score >= 50 first, a 74 would match that instead and everyone above 50 would get a C.

The condition can be any expression that produces a true or false value, so you can combine checks with the logical operators from operators:

js
const age = 20;
const hasTicket = true;

if (age >= 18 && hasTicket) {
  console.log("Enjoy the show."); // Enjoy the show.
} else {
  console.log("Entry refused.");
}

An if statement evaluates a condition and runs its block when the result is true, with optional else if and else branches for the other cases. The chain is ordered: JavaScript evaluates conditions top to bottom and executes the first block whose condition is true, then exits the whole chain.

js
const score = 74;

if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 70) {
  console.log("Grade: B"); // Grade: B
} else if (score >= 50) {
  console.log("Grade: C");
} else {
  console.log("Grade: F");
}

Two properties of this shape are worth holding onto. First, the branches are mutually exclusive: exactly one runs. That is why the boundaries here only need one side each. The score >= 70 branch does not need score < 90 guarding it, because reaching that line already means the score >= 90 test failed. Adding the redundant upper bound is a common instinct and it makes the chain harder to read, not safer.

Second, the condition is coerced to a boolean, so it does not have to be a literal comparison. Any expression works, and JavaScript applies its truthy and falsy rules to decide. That flexibility is useful and occasionally a trap, which is why the advanced tier of this chapter comes back to it in detail below.

js
const age = 20;
const hasTicket = true;

if (age >= 18 && hasTicket) {
  console.log("Enjoy the show."); // Enjoy the show.
} else {
  console.log("Entry refused.");
}
JunoRunning code only when a condition is true An if runs its block only when the condition in the parentheses is true, and else gives you a fallback for when it is not. Add else if when there are more than two outcomes. JavaScript checks from the top and stops at the first match, so put your conditions in an order that makes sense.
JunoRunning code only when a condition is true An if / else if / else chain runs the block for the first true condition and skips the rest, so order is not decoration, it is logic. Ranges go high to low for exactly that reason. The condition can be any expression that resolves to true or false, so combine checks with the logical operators when you need to.
JunoRunning code only when a condition is true The chain is mutually exclusive and evaluated top to bottom, so each branch only needs the boundary that the branches above it have not already ruled out. Adding the redundant upper bound reads as caution but is really only noise. And the condition is coerced to a boolean, which is a feature until an unexpected value slips through, so keep the truthy and falsy rules in mind.

Choosing a value with the ternary operator

Sometimes an if / else does one small job: pick between two values. For that there is a shorter form called the ternary operator. You write a condition, a ?, the value to use when it is true, a :, and the value to use when it is false.

js
const age = 20;
const message = age >= 18 ? "Welcome" : "Too young";

console.log(message); // Welcome

Read it as a question: is age >= 18? If yes, message becomes "Welcome"; if no, it becomes "Too young". That is the same decision an if / else would make, in one line.

The ternary is at its best when you are choosing a value to store or print. When a branch needs to do several things, an if statement is clearer, so reach for the ternary only when each side is a single value.

The ternary operator picks between two values based on a condition: condition ? valueIfTrue : valueIfFalse. Unlike an if statement, it is an expression, so it produces a value you can assign or drop straight into a template literal.

js
const score = 82;
const passed = score >= 60 ? "pass" : "fail";

console.log(`Result: ${passed}`); // Result: pass

This is where it earns its place. Assigning one of two values to a variable is a job the ternary does in a line, while the if version spreads the same logic across five and repeats the assignment in both branches:

js
// The if version does the same thing with more ceremony:
let passed;
if (score >= 60) {
  passed = "pass";
} else {
  passed = "fail";
}

Where it stops helping is when the branches do more than produce a value. If either side needs to run several statements, or you find yourself nesting one ternary inside another, the readability you gained is gone. At that point an if statement is the clearer choice.

The ternary operator is the only operator in JavaScript that takes three operands: condition ? valueIfTrue : valueIfFalse. The distinction that makes it useful is that it is an expression, not a statement. It evaluates to a value, so it slots into places a statement cannot go: the right side of an assignment, an argument, a template literal.

js
const score = 82;
const label = score >= 60 ? "pass" : "fail";

console.log(`Result: ${label}`); // Result: pass

Use it when the whole job is selecting between two values, and prefer it over an if there because it keeps the assignment in one place instead of repeating it in both branches. Reach for a ternary to choose a value, not to run a branch.

The failure mode is nesting. A ternary whose branches are themselves ternaries technically works, but it forces the reader to parse right to left and hold several conditions in their head at once:

js
// Hard to read. Prefer an if chain or a switch here.
const grade = score >= 90 ? "A" : score >= 70 ? "B" : score >= 50 ? "C" : "F";

That line produces the right answer and costs the next reader real effort. When a decision has more than two outcomes, an if chain or a switch states it far more plainly, which is the next section.

JunoChoosing a value with the ternary operator The ternary is a compact way to pick between two values: condition ? valueIfTrue : valueIfFalse. It shines when you are choosing something to store or print. If a branch needs to do more than hand back a single value, use a full if instead, it will read more clearly.
JunoChoosing a value with the ternary operator A ternary is an expression, so it gives back a value you can assign or drop into a template literal, which an if statement cannot do. That makes it the clean choice for picking one of two values, since the if version repeats the assignment in both branches. Once the branches do real work, or you start nesting ternaries, switch back to an if.
JunoChoosing a value with the ternary operator The ternary is an expression, so it goes where a statement cannot: an assignment, an argument, a template literal. Use it to choose a value, not to run a branch, and it stays readable. Nest one inside another and it stops paying its way, so a multi-outcome decision belongs in an if chain or a switch.

Comparing one value against fixed cases with switch

When you are checking one value against a list of set possibilities, a switch statement can be tidier than a long if / else if chain. You give it the value to check, then list the cases to compare it against.

js
const day = "Tue";

switch (day) {
  case "Sat":
    console.log("Weekend");
    break;
  case "Sun":
    console.log("Weekend");
    break;
  default:
    console.log("Weekday"); // Weekday
}

The switch compares day to each case in turn. When it finds a match, it runs that case's code. break tells it to stop; without it, JavaScript keeps running into the next case. default is the fallback that runs when no case matches, like the final else in a chain.

Forgetting break is the classic switch mistake. Leave it off and your code runs the matching case and then every case below it until it hits a break or the end, which is almost never what you meant.

A switch statement compares one value against a set of fixed cases. It reads more clearly than a long if / else if chain when every branch is testing the same value for equality.

js
const status = "shipped";

switch (status) {
  case "pending":
    console.log("Order received.");
    break;
  case "shipped":
    console.log("On its way."); // On its way.
    break;
  case "delivered":
    console.log("Delivered.");
    break;
  default:
    console.log("Unknown status.");
}

Three things carry the logic. case marks a value to match, break ends the matched case, and default handles anything unmatched. The match uses strict equality (===), so case "1" will not match a number 1.

The gotcha is fall-through. A case without a break does not stop; execution keeps going into the following cases until it hits a break or the end of the switch. That is usually a bug, but it is also occasionally useful when several cases share one outcome:

js
const day = "Sat";

switch (day) {
  case "Sat":
  case "Sun":
    console.log("Weekend"); // Weekend
    break;
  default:
    console.log("Weekday");
}

Here "Sat" matches and falls through to the shared block for "Sun", so both weekend days run the same line. That is fall-through used on purpose, and it only reads clearly because the empty cases stack directly above the shared block.

A switch statement takes a single value and compares it against a series of case labels, running the block of the first that matches. The comparison is strict equality (===), which matters: case "1" does not match the number 1, and there is no type coercion to save you.

js
const status = "shipped";

switch (status) {
  case "pending":
    console.log("Order received.");
    break;
  case "shipped":
    console.log("On its way."); // On its way.
    break;
  case "delivered":
    console.log("Delivered.");
    break;
  default:
    console.log("Unknown status.");
}

The mechanism people underestimate is fall-through. A switch does not run one case and stop; it jumps to the matching label and then executes straight down until it hits a break or the closing brace, ignoring case boundaries along the way. A missing break runs every case below the match. That is the source of most switch bugs, and it is also the one deliberate use of the behaviour, stacking labels so several values share a block:

js
const day = "Sat";

switch (day) {
  case "Sat":
  case "Sun":
    console.log("Weekend"); // Weekend
    break;
  default:
    console.log("Weekday");
}

default does not have to come last, though putting it last is the convention that keeps switches readable. Note also that a switch only tests one value for equality, so a chain of range checks like the grade example earlier cannot become a switch. That limit is exactly what tells you when a switch is the right tool, which the last part of this tier picks apart.

JunoComparing one value against fixed cases with switch A switch checks one value against a list of case options and runs the one that matches, with default as the catch-all. Always end each case with break, or JavaScript keeps running into the cases below it. That missing break is the mistake almost everyone makes at least once.
JunoComparing one value against fixed cases with switch Use a switch when every branch is testing the same value for equality against fixed cases, and remember it matches with ===, so "1" is not 1. Each case needs a break or it falls through into the next one. That fall-through is a bug by default, but stacking empty cases above a shared block is the one time you want it.
JunoComparing one value against fixed cases with switch A switch matches one value with strict equality and then runs downward until a break, ignoring case boundaries, so a missing break runs every case below the match. Stacking labels above a shared block is the one deliberate use of that. Because it only tests equality on a single value, a set of range checks stays an if chain.

Picking the right tool

You now have three ways to make a decision, and the choice is mostly about fit. Use an if / else if / else chain for most branching, especially when the conditions are ranges or combinations. Use a ternary when you are picking one of two values to store or print. Use a switch when you are checking one value against a handful of fixed options.

None of these is more advanced than the others. Reach for the one that states your decision most plainly, and if you are unsure, an if statement is always a safe default.

The three forms overlap, so the useful skill is matching the tool to the shape of the decision. An if chain handles anything, and it is the right call whenever conditions are ranges (score >= 70) or combinations (age >= 18 && hasTicket). A ternary is for choosing between two values in one expression. A switch fits when a single value is tested for equality against several fixed cases, like a status or a day name.

One rule prevents a lot of tangled code: reach for the shape that reads plainest, not the shortest. A nested ternary is shorter than an if chain and worse to read, so length is not the goal. Clarity for the next person, who is often you in a month, is.

Three tools, and the boundaries between them come down to the shape of the decision. An if chain is the general form: it handles ranges, combinations, and anything a condition can express, so it is the default whenever the branches are not all equality tests on one value. A ternary narrows to a single job, choosing one of two values as an expression, and pays off only while both branches stay simple. A switch fits when one value is compared for equality against a fixed set of cases.

There is a fourth option worth naming even though it is out of scope here: when you are mapping a value to a result, a lookup object often beats all three. A switch that only assigns a value per case, or a long if chain of value === "x" tests, is frequently clearer as a plain object you index into. You will meet that pattern once objects are on the table; for now, notice when a switch is really a table in disguise.

Two more advanced habits round this out. The first is truthy and falsy values, because a condition does not need to be a comparison. JavaScript coerces the condition to a boolean, and the values that come out false are a fixed, short set: false, 0, "" (the empty string), null, undefined, and NaN. Everything else is truthy. That is why if (name) is a common shorthand for "name is not empty", and also why it can surprise you: a value of 0 is a real number but a falsy condition, so if (count) skips the block when count is 0. Know the falsy set and these checks stop being guesswork.

The second is the guard clause. When a decision is really about bailing out early, a deeply nested if reads worse than checking the exit condition first and stopping. That "check the bad case, stop, then continue with the good path" shape is the guard clause, and it keeps the main logic un-indented instead of buried inside branches. The stopping part usually means returning out of a function, which is the next chapter, functions, so the full pattern lands there. The idea to carry in now is that flattening a decision by handling the exceptional case first is almost always clearer than nesting the normal case deeper and deeper.

JunoPicking the right tool Use an if chain for most decisions, a ternary when you are picking one of two values, and a switch when you are matching one value against fixed options. None of them is fancier than the others. Pick whichever states your decision most clearly, and default to if when you are not sure.
JunoPicking the right tool Match the tool to the shape: if for ranges and combinations, a ternary for choosing one of two values, a switch for equality against fixed cases. Reach for the form that reads plainest, not the shortest, because a nested ternary is short and hard to follow. The next reader is often you, so write for them.
JunoPicking the right tool The if chain is the general tool, the ternary is for picking one of two values, and the switch is for equality against a fixed set, with a lookup object often beating a value-mapping switch once you have objects. Keep the falsy set in your head (false, 0, "", null, undefined, NaN) so a bare if (count) does not surprise you when the count is zero. And when a decision is really an early exit, handle the bad case first and flatten the nesting; the full guard clause lands with functions.

Where conditionals go from here

Conditionals are how a program stops being a fixed script and starts responding to its input. You will use them constantly, and they combine with everything around them: the true-or-false answers come from operators, and the decisions you make here start to pay off once you can run them repeatedly over data with loops and package them up in functions.

The next chapter, loops, adds the other half of control flow: not only choosing whether to run code, but running it again and again until a condition tells you to stop.