Decisions with if and when
Comparing values, choosing between two paths, and treating if and when as expressions that produce a value instead of only running code.
A condition is a question with a yes or no answer
A comparison produces a Boolean: temperature > 30 is true when the number is bigger, and false when it is not. The usual six comparisons are there — greater than, less than, and the two versions of equal and not equal — and == compares values rather than memory addresses, so two equal strings count as equal. >= and <= include the boundary, which is where an off-by-one in a band starts.
An if takes that Boolean and picks one of two paths. Add else if for a third possibility and a final else for everything left over. Only one branch runs, and the test is read from the top, so put the narrow case first and the general one last. A branch that can never run is not harmless: it makes the reader look for the case that reaches it.
If is an expression, so it can hand back a value
In Kotlin an if is not a statement that happens to run: it is an expression, so the whole thing has a value. That means val label = if (temperature > 25) "warm" else "cool" is a complete line, with the value chosen on the spot. The two branches must produce compatible types, because the compiler has to know the type of label before the program runs.
Use that when you are choosing a value, and the block form when each branch has work to do. Both spellings are the same idea, and the shorter one is often the clearer one. The block form is not worse; it is simply for branches that print, assign or call something rather than answer with a single expression.
When, for more than two paths
when replaces a long ladder of else if. Give it a subject in parentheses and it compares each branch with that value: when (day) { 1, 7 -> "weekend" }. Leave the subject out and each branch becomes its own condition, which reads like a small table of rules. With a subject, the branches are about one value; without one, they are a list of independent questions.
A branch can test membership with in, so in 2..5 catches every weekday in one line. When the value in front of the branches is a plain one — a number, text, a Boolean — the compiler also wants a branch for every case it can think of, and else covers the rest. That exhaustiveness check is why a when over a Boolean needs both true and false, or an else.
Order is part of the meaning
A when reads its branches from top to bottom and stops at the first match. A broad branch placed above a narrow one hides the narrow one completely, and the compiler may not warn because both are valid. The habit is to write the most specific test first and let else catch the remainder.
An if ladder has the same rule, but the danger is easier to see because each branch repeats its condition. With when, the conditions are separated from the value, so a reordering looks harmless. Read the list as a sequence of sieves: each branch catches what the ones above it let through.
Two paths, then many
fun main() {
val temperature = 27
if (temperature > 30) {
println("Hot")
} else {
println("Mild")
}
// The whole if has a value.
val label = if (temperature > 25) "warm" else "cool"
println(label)
val day = 3
val kind = when (day) {
1, 7 -> "weekend"
in 2..5 -> "weekday"
else -> "unknown"
}
println(kind)
}One temperature, three questions
Run it and check each printed line against the value in the code. Then set temperature to 40 and to 10, and change the day to 7 so you can watch a different branch win.
Conditions are values before they are branches
A comparison such as total >= 1000 is an expression: it produces true or false, and that value can be stored, printed or passed to a function. if and when consume that value. When you use either one as the right-hand side of a name, every branch has to produce a value of the same type, because the compiler has to know the type of that name before the program runs.
when checks its branches from top to bottom and stops at the first match, so order is part of the meaning. A broad branch placed above a narrow one hides the narrow one completely. With a subject, when can also use in for membership and a comma for alternatives; without a subject, each branch is simply a condition, read in order.
Three mistakes are common here. A single equals sign assigns; a comparison needs ==. A when used as a value needs an else branch unless the compiler can prove every case is covered. And branches are tried in order, so else written first makes every branch below it unreachable — the compiler may warn, but the logic is already wrong.
Try these next
- Give a parcel three weight bands with
when, then change one boundary and predict which branch changes. - Store an
ifin avaland print it, then rewrite the same choice as awhen. - Write a
whenwith a branch that can never run, and explain why to yourself before deleting it.
Shipping bands for a small order
Run it and check which band the subtotal falls into. Then try a subtotal of 999 and one of 2000, and move the 1000 band above the 2000 band to see what order does.
Change one number in the example and predict the output before you run it. Conditions are where a program stops being obvious, so the habit of predicting first pays for itself immediately.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What can an if do in Kotlin besides choose a branch?
