Conditionals and Decisions
How a program chooses what to do next — `if`, `else`, `else if`, `switch`, and how to keep them readable.
So far our programs have run their statements straight through from top to bottom. Every line, every time. The instant a program starts making decisions — "if the user is logged in, show their name; otherwise show 'Sign in'" — it starts looking like real software.
That capacity for decision-making is called control flow, and
the most fundamental tool for it is the if statement.
The if statement
The basic shape:
if (condition) {
// statements that run only if condition is truthy
}The condition is any expression. If its value is truthy, the
block runs; otherwise it is skipped entirely.
Run the program. Then change age to 19 and run again. The
console.log("You can vote.") line is skipped or executed
depending entirely on the condition.
if/else
To do one thing when a condition is true and a different thing
otherwise, add else:
Visually:
That diamond is a decision point in the flow of the program. Every conditional in your code corresponds to one of these diamonds.
if/else if/else
When there are several mutually exclusive cases:
The runtime checks each condition top-to-bottom. The first one
that is true runs; all the rest are skipped. The final else (no
condition) catches everything left over.
Order matters. If you wrote the conditions starting from
score >= 60 first, every passing score would be labelled "D"
because that condition matches first.
Guard clauses: prefer early returns
A common pattern in modern code is to write guard clauses at
the top of a function — handle the "bad" or "trivial" cases first
and return early, so the main logic stays at the top level
without a giant pyramid of nested ifs.
Compare. Both functions compute the same thing.
The second version is far easier to read, especially as logic grows. Use guard clauses whenever you can.
Boolean style: don't compare to true
A small but important style point:
// awkward
if (isLoggedIn === true) { ... }
// clear
if (isLoggedIn) { ... }Booleans are already booleans — you don't need to test whether
they equal true. Same for the negation: write if (!isLoggedIn),
not if (isLoggedIn === false).
switch
When you are dispatching on the value of a single variable, the
switch statement is sometimes a clearer alternative to a long
if/else if chain:
A few things to know about switch:
- The comparison is strict equality (
===), so the type matters:case "1"is not the same ascase 1. - Without a
break(orreturn), execution falls through to the nextcase. This is sometimes useful (as above, where several days share one outcome), but it is a common source of bugs. - The
defaultclause is optional and acts likeelse.
Many modern codebases avoid switch and use plain if/else if
or a lookup object. Use it if it makes a particular piece of code
clearer; otherwise, regular conditionals are fine.
A lookup table: a "no-if" alternative
When all your if/else if arms do the same shape of thing
(returning a value), an object lookup is often cleaner than
any of the above. Build a small table, look up the answer.
This pattern scales well. Adding a new role is a one-line edit to the table; no code changes.
Combining conditions
You will often need to test more than one thing at once. That is where the logical operators from the previous chapter come in.
Three guard clauses, each handling one failure case in turn. This
is much easier to read than one big condition like
if (age >= 25 && hasLicence && hasCard) { ... } else { ... }
because the reason for the rejection is preserved.
Putting it together: a FizzBuzz warm-up
The classic beginner exercise: print the numbers 1 to 30, but
replace multiples of 3 with "Fizz", multiples of 5 with
"Buzz", and multiples of both with "FizzBuzz".
The order of the conditions is critical: we test the most
specific case (% 15 === 0) first, then the more general cases.
If we reversed the order, every multiple of 15 would print "Fizz"
and the most specific case would never run.
(We have not formally met for loops yet — that is the next page.
For now, just take it on faith that the body runs once for each
n from 1 to 30.)
Challenge
Write a function action(color) that returns:
"stop"if the colour is"red""slow down"if the colour is"yellow""go"if the colour is"green""unknown signal"for anything else (including the empty string)
Use comparisons and conditionals, not a lookup table — practise the syntax.
Why do programmers often prefer "guard clauses" (early returns at the top of a function) over deeply nested if statements?
Guard clauses run faster
Guard clauses use less memory
Guard clauses keep the main logic at the top indent level, making the function easier to read and easier to extend
Guard clauses are the only way to return from a function