Else if
if and else choose between two paths. Many decisions have more than two: a grade is an A, a B, a C or a fail; a temperature is a heat warning, a frost warning or nothing at all. else if adds another condition to an if, and a chain of them chooses among as many cases as you need.
A chain of conditions
let score: int32 = 85;
if score >= 90 {
PrintLine("{} earns an A", score);
} else if score >= 80 {
PrintLine("{} earns a B", score);
} else if score >= 70 {
PrintLine("{} earns a C", score);
} else {
PrintLine("{} needs another try", score);
}
The chain tests its conditions from the top, runs the block of the first one that holds, and skips everything after it. A final plain else catches whatever no condition matched. So at most one block in a chain runs — exactly one, when the chain ends with else.
flowchart LR
s(["score = 85"]) --> a{"score >= 90?"}
a -- "yes" --> A["an A"]
a -- "no" --> b{"score >= 80?"}
b -- "yes" --> B["a B"]
b -- "no" --> c{"score >= 70?"}
c -- "yes" --> C["a C"]
c -- "no" --> E["another try"]85 fails the first test and passes the second, so the chain prints 85 earns a B and never looks at >= 70.
Each test may assume the ones above it failed
By the time the chain reaches score >= 80, the score is already known to be below 90 — otherwise the first block would have run. So the chain needs no score >= 80 && score < 90. Each condition only has to draw the next line, not repeat the ones above it.
Order is part of the meaning
Because the first match wins, the order of a chain changes what it does. Here are the same conditions, loosest first:
if score >= 70 {
PrintLine("reordered chain: a C");
} else if score >= 80 {
PrintLine("reordered chain: a B");
} else if score >= 90 {
PrintLine("reordered chain: an A");
}
85 is also at least 70, so the first test already holds and the chain stops there. Worse, the >= 80 and >= 90 blocks can now never run for anyone: every score that would reach them has already been caught by >= 70. The compiler cannot tell this from a deliberate choice, so it is up to you. A good rule for ranges like these: test the narrowest case first.
A chain may run nothing
Without a final else, it is possible for no condition to hold:
let temperature: int32 = 18;
if temperature > 30 {
PrintLine("heat warning");
} else if temperature < 0 {
PrintLine("frost warning");
}
PrintLine("{} degrees needs no warning", temperature);
Leave out the else only when "nothing happens" is a real answer, as it is here.
A chain, or separate ifs?
A chain and a row of separate if statements look alike but behave differently:
| Shape | Tests | Blocks that run |
|---|---|---|
if … else if … else if … | until the first condition that holds | at most one |
if … then if … then if … | every condition, every time | every one whose test holds |
if temperature > 10 {
PrintLine("above 10");
}
if temperature > 15 {
PrintLine("above 15");
}
Both of these print, because they are two separate statements. Written as a chain, only above 10 would.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// `else if` adds another condition to an `if`, and a chain of them chooses among many cases. The
// chain tests its conditions from the top, runs the block of the first one that holds, and skips
// everything after it. So at most one block in a chain runs, and a final plain `else` catches
// whatever no condition matched.
//
// Because the first match wins, the order of a chain is part of its meaning. The two chains below
// test the same score with the same conditions, in different orders, and disagree.
import Io::PrintLine;
func Main() -> int {
let score: int32 = 85;
// Each test may assume the ones above it failed: by the `>= 80` line, the score is known to be
// below 90, so the chain needs no `score >= 80 && score < 90`.
if score >= 90 {
PrintLine("{} earns an A", score);
} else if score >= 80 {
PrintLine("{} earns a B", score);
} else if score >= 70 {
PrintLine("{} earns a C", score);
} else {
PrintLine("{} needs another try", score);
}
// The same conditions, loosest first. 85 is also at least 70, so the first test already
// holds, the chain stops there, and the `>= 80` and `>= 90` blocks can never run for anyone.
if score >= 70 {
PrintLine("reordered chain: a C");
} else if score >= 80 {
PrintLine("reordered chain: a B");
} else if score >= 90 {
PrintLine("reordered chain: an A");
}
// Without a final `else`, a chain may run nothing at all.
let temperature: int32 = 18;
if temperature > 30 {
PrintLine("heat warning");
} else if temperature < 0 {
PrintLine("frost warning");
}
PrintLine("{} degrees needs no warning", temperature);
// Compare a chain with separate `if` statements: separate statements test every condition,
// and each one that holds runs. Both of these print, where a chain would print only the first.
if temperature > 10 {
PrintLine("above 10");
}
if temperature > 15 {
PrintLine("above 15");
}
return 0;
}
Run it
cd Examples/ControlFlow/ElseIf
rux run
85 earns a B
reordered chain: a C
18 degrees needs no warning
above 10
above 15
Common mistakes
With
score >= 70 at the top, every passing score stops there, and the A and B blocks become unreachable. The compiler does not warn about it — the program simply gives the wrong answer. Put the narrowest condition first.ifs where only one case should win.Two separate
if statements both run when both conditions hold. When the cases are meant to exclude each other, join them with else if.Try it yourself
- Change
scoreto95,72and40and predict each line before you run. - Add an
A+grade for scores of 97 and above. Where in the chain does it have to go? - Rewrite the temperature check so it prints
"mild"when there is no warning, using a finalelse. - Turn the two separate
ifs at the end into one chain, and check that only one line prints.