when — the clean multi-way choice
Five outcomes written as else if is a wall of repetition that hides its own bugs. Kotlin's when says the thing being tested once, lists the answers underneath, and hands back a value if you want one.
The same question, asked five times
Here is a die roll turned into a message, written with the tools you already have:
1if (roll == 1) {
2 println("Ouch")
3} else if (roll == 2) {
4 println("Meh")
5} else if (roll == 3) {
6 println("Meh")
7} else {
8 println("Nice")
9}It works. It is also horrible, and not just because it is long. The name roll appears three times and == appears three times, which means there are six chances to typo something that still compiles. Miss one = and Kotlin catches you; type rol1 and it might not.
There is a better tool. It says the interesting thing — roll — exactly once.
1when (roll) {
2 1 -> println("Ouch")
3 2, 3 -> println("Meh")
4 else -> println("Nice")
5}Same behaviour, half the text, and nothing repeated.
Think of the noticeboard in a doctors' surgery:
- Blood tests → Room 2
- Vaccinations → Room 4
- Anything else → ask at reception
Nobody wrote "if you are here for blood tests then go to room 2, and if you are not here for blood tests but you are here for vaccinations…". The board states the thing being sorted once at the top, then lists each answer and where it leads.
is that noticeboard. The value in the round brackets is what is being sorted. Each line is one answer and what happens. else is the reception desk — where anything unlisted goes.
The shape of it
1when (roll) {
2 1 -> println("One")
3 6 -> println("Six")
4 else -> println("Something else")
5}Four things to notice:
- The value being tested goes in the round brackets, once.
- Each line is
answer -> what to do. The arrow is a minus sign and a greater-than sign, with no space between them. - Kotlin checks the lines in order and runs the first one that matches, then stops. Nothing below it is looked at.
elsecatches everything that did not match. It always goes last.
If a branch needs more than one line, wrap it in curly brackets:
1when (roll) {
2 6 -> {
3 println("Six!")
4 println("Roll again")
5 }
6 else -> println("Pass the dice")
7}Matching more than one value at a time
Two things when can do that an else if chain does clumsily.
Several values on one line, separated by commas:
1when (day) {
2 "Sat", "Sun" -> println("Weekend")
3 else -> println("School")
4}A whole with in, which is two dots between the ends:
1when (score) {
2 in 90..100 -> println("Gold")
3 in 60..89 -> println("Silver")
4 else -> println("Try again")
5}90..100 means every whole number from 90 to 100, including both ends. That inclusive detail matters, and Lesson 1.7 pushes on it a lot harder.
when that hands back a value
Like , when is an — it produces a value you can store:
1val medal = when (score) {
2 in 90..100 -> "Gold"
3 in 60..89 -> "Silver"
4 else -> "None"
5}
6println(medal)Every branch produces a String, so medal is a String. This form is where when really earns its place: one name, one value, no chance of forgetting to set it on some forgotten path.
And that leads to the rule you will meet most often:
When
whenproduces a value, it must cover every possibility.
Leave the else off and you get 'when' expression must be exhaustive, add necessary 'else' branch. Kotlin is asking a fair question: if the score is 12 and there is no matching branch, what exactly should go into medal?
Used as a statement — where you are just doing something rather than producing a value — the else is optional. Kotlin only insists when a value has to come out. Adding one anyway is usually a good idea; it is where "that should never happen" cases go to be noticed.
when with no value in the brackets
There is a second form. Leave the round brackets off entirely and each branch becomes its own full :
1val temp = 31
2val advice = when {
3 temp > 30 -> "Stay in the shade"
4 temp > 20 -> "Lovely"
5 temp > 10 -> "Bring a jacket"
6 else -> "Stay in"
7}
8println(advice)This is the tidy replacement for a long else if chain when the tests are about different things, or are not simple equality. Order still decides everything: 31 matches the first line and the rest are never considered.
Reading one line by line
- Open Pocket Studio, tap Projects, open KotlinLab, then open
Lab.kt. - Clear the inside of
mainand type in the program from the walkthrough above. - Tap Run. You should see
Meh, thenThat roll is not the biggest. - Change
val roll = 3toval roll = 5and run. Now the range branch fires:Good. - Try
val roll = 6. You getSix!andThat roll is the biggest. - Try
val roll = 9. Theelsecatches it:Not a die. - Now move the
else -> println("Not a die")line up so it sits directly underwhen (roll) {. Press Run. Pocket Studio warns that the branches below it can never be reached, becauseelsematches everything. Move it back to the bottom. - Finally, delete the
elsefrom the secondwhen(the one on line 10) and run. Read the error about exhaustiveness, then put it back.
when is being used to produce a value, but there is at least one input it has no answer for.else -> something as the last branch. Every route through the expression has to end up with a value.value -> action. The arrow is a minus sign followed by a greater-than sign, written together as ->."six", for instance.roll is an Int, the branches must be numbers: 6 -> ..., not "6" -> ....2, 3 -> ....when block ends.- names the value being tested once, then lists each answer as
value -> action. - Branches are checked top to bottom and the first match wins; everything below is skipped.
- One branch can cover several values with commas, or a whole with
in 1..5. - Used as an — on the right of an
=— awhenmust be exhaustive, which in practice means it needs anelse. - With no value in the round brackets, each branch becomes its own — the tidy replacement for a long
else ifchain. - Next: repeating things.
while,for, ranges, and the counting mistake that has shipped in more software than any other.