Lesson 11 / 21
Conditions and Selection
`if` chains and guard clauses, the ternary operator, the `switch` statement's strict equality, fall-through behavior, and scope in case blocks.
Contents
The value model is complete. This topic covers building program flow with those values: which code runs under which condition, how repetition is written, and how work is split into functions. The Programming Fundamentals course’s Conditional Branching lesson established the purpose of these structures; here, JavaScript’s own particular rules are covered.
Three things about conditional selection are specific to the language: how a condition
is converted to a boolean value (previous lesson), the ternary operator being an
expression, and which equality switch uses.
Condition Chains and Guard Clauses
The if statement converts the expression in parentheses to a boolean value and runs
the body if it is truthy. The chain continues with else if.
Guard clauses — conditions placed at the start of a chain that return early on an invalid case — rescue the real logic from nested blocks:
function classify(temperature) { if (temperature == null || Number.isNaN(temperature)) { return "invalid"; } if (temperature < 18) { return "low"; } else if (temperature < 22) { return "normal"; } else { return "high"; } } for (const value of [17.9, 18, 21.5, 22, 23, null, NaN]) { console.log(String(value).padEnd(5), classify(value)); }
17.9 low 18 normal 21.5 normal 22 high 23 high null invalid NaN invalid
The guard clause combines two checks. The == null spelling is the exception defined
in the Equality Comparisons lesson: it covers both the null and undefined cases.
Number.isNaN catches an invalid number; because NaN passes no comparison, without
this guard classify(NaN) would silently give "high" — all three comparisons would
come out false and flow would fall to the final else branch.
Which side a boundary value falls on is read from the output: 18 went to normal, 22
went to high. The choice of comparison operator (< versus <=) sets these boundaries
and is part of the definition.
Curly braces are not required for single-statement bodies, but this course always writes them. The reason is that a second statement added later would otherwise silently fall outside the condition.
The Ternary Operator
if is a statement: it produces no value, it directs flow. The ternary operator is an
expression: it produces a value and can be written anywhere a value is expected.
const temperature = 21.5; const status = temperature >= 22 ? "high" : temperature >= 18 ? "normal" : "low"; console.log(status); console.log(`${temperature} degrees: ${temperature > 20 ? "above threshold" : "below threshold"}`);
normal 21.5 degrees: above threshold
The second line shows the concrete benefit of being an expression: a condition was
placed directly inside a template literal. An if statement could not have been written
there.
A chained ternary associates to the right; the second question mark sits inside the
first expression’s false branch. This spelling quickly loses readability past two
branches. This course’s rule: a ternary is chained at most once; anything more is
written as an if chain or a mapping object.
The switch Statement
switch compares a value against multiple constants. Matching is done with strict
equality — one of the three operators from the Equality Comparisons lesson, ===.
function unit(field) { switch (field) { case "temperature": return "C"; case "humidity": return "%"; case "pressure": return "hPa"; default: return "unknown"; } } console.log(unit("temperature"), unit("humidity"), unit("wind"));
C % unknown
Using strict equality removes any expectation of coercion:
function check(value) { switch (value) { case 1: return "number one"; case "1": return "text one"; default: return "no match"; } } console.log(check(1), "|", check("1"), "|", check(true));
number one | text one | no match
The number 1 and the text "1" went to separate branches. The value true matched
neither — under loose equality true == 1 would be true, but switch does not use
loose equality.
Fall-Through
The switch statement starts at the matching case label and flows downward until
it meets a break or return. This behavior is called fall-through, and when done
unintentionally it produces a silent bug:
function label(field) { let result = ""; switch (field) { case "temperature": result = "temperature (C)"; case "humidity": result += " humidity (%)"; break; default: result = "unknown"; } return result; } console.log(JSON.stringify(label("temperature"))); console.log(JSON.stringify(label("humidity"))); console.log(JSON.stringify(label("pressure")));
"temperature (C) humidity (%)" " humidity (%)" "unknown"
On the first line, flow fell from the first branch into the second and the two texts
concatenated. This was not the writer’s intent. The outputs were written with
JSON.stringify; otherwise the leading space on the second line would not be visible.
Used deliberately, fall-through binds multiple values to the same branch:
function group(station) { switch (station) { case "A1": case "A2": return "group A"; case "B1": return "group B"; default: return "unknown"; } } console.log(group("A1"), "|", group("A2"), "|", group("B1"), "|", group("C9"));
group A | group A | group B | unknown
Empty case labels have no body; flow falls straight through to the next one. This is
an accepted use of the language and is not hard to read — falling from a branch with
a body into another will not be written in this course.
Scope in Case Blocks
The entire switch body is a single block. Separate case labels do not open
separate scopes; this is why declaring the same name in two branches gives a parsing
error:
switch (1) { case 1: const x = 1; break; case 2: const x = 2; break; }
SyntaxError: Identifier 'x' has already been declared
The redeclaration rule from the Variable Declarations lesson shows up here in an unexpected place. The fix is to wrap each branch in its own curly braces:
switch (1) { case 1: { const x = "first"; console.log(x); break; } case 2: { const x = "second"; console.log(x); break; } }
first
Choosing a Selection Structure
The choice among the three structures is made according to the shape of the comparison.
switch fits when a single value is compared against constants. Range checks, compound
conditions, or a comparison other than equality call for an if chain.
Constant mappings offer a third option: they can be written as key–value pairs in an object and read directly.
const UNITS = { temperature: "C", humidity: "%", pressure: "hPa" }; function unit(field) { return UNITS[field] ?? "unknown"; } console.log(unit("temperature"), unit("wind"));
C unknown
This spelling keeps its readability as the mapping grows and separates data from code.
Using ?? is deliberate: if a key in the mapping had a value of 0 or an empty
string, || would have dropped that to the default too.
Summary
- Guard clauses return early on an invalid case and rescue the real logic from nested
blocks; without a
NaNguard, flow silently falls to the final branch. - The ternary operator is an expression and can be written anywhere a value is expected; it is chained at most once.
switchmatches with strict equality; it performs no coercion.- Flow starts at the matching label and falls through until it meets
breakorreturn; bodyless labels bind multiple values to the same branch. - The
switchbody is a single block; to declare a name in a branch, wrap each branch in its own curly braces. - Constant mappings written as objects substitute for
switchand take a default with??.
Next Step
Conditions classified a single record. Processing the entire measurement list requires repetition. The next lesson covers loop forms: the counted loop, the conditional loop, and two different collection loops. The difference between the two collection loops — one gives keys, the other gives values — produces unexpected results on arrays.
To keep your progress and take notes, Log in
My notes
Log in to take notes.