Pocket Studio Academy
HomePart 11.6

when — the clean multi-way choice

Free lesson9 min read·4 questions

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:

kotlin
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.

kotlin
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 it like this

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

kotlin
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.
  • else catches everything that did not match. It always goes last.

If a branch needs more than one line, wrap it in curly brackets:

kotlin
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:

kotlin
1when (day) {
2  "Sat", "Sun" -> println("Weekend")
3  else -> println("School")
4}

A whole with in, which is two dots between the ends:

kotlin
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:

kotlin
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 when produces 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?

Tip

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 :

kotlin
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

Try it in Pocket Studio
  1. Open Pocket Studio, tap Projects, open KotlinLab, then open Lab.kt.
  2. Clear the inside of main and type in the program from the walkthrough above.
  3. Tap Run. You should see Meh, then That roll is not the biggest.
  4. Change val roll = 3 to val roll = 5 and run. Now the range branch fires: Good.
  5. Try val roll = 6. You get Six! and That roll is the biggest.
  6. Try val roll = 9. The else catches it: Not a die.
  7. Now move the else -> println("Not a die") line up so it sits directly under when (roll) {. Press Run. Pocket Studio warns that the branches below it can never be reached, because else matches everything. Move it back to the bottom.
  8. Finally, delete the else from the second when (the one on line 10) and run. Read the error about exhaustiveness, then put it back.
Error Doctor5 common errors
e: 'when' expression must be exhaustive, add necessary 'else' branch
MeansThis when is being used to produce a value, but there is at least one input it has no answer for.
FixAdd else -> something as the last branch. Every route through the expression has to end up with a value.
e: Expecting '->'
MeansA branch has a value but no arrow, so Kotlin cannot tell where the answer ends and the action begins.
FixEvery branch is value -> action. The arrow is a minus sign followed by a greater-than sign, written together as ->.
e: Incompatible types: String and Int
MeansA branch is testing for a value of a completely different type from the one in the brackets — comparing a number against "six", for instance.
FixMatch the type of the subject. If roll is an Int, the branches must be numbers: 6 -> ..., not "6" -> ....
w: Duplicate label in when
MeansThe same value appears in two different branches. Only the first can ever run, so the second is dead code — and it is usually a copy-paste slip.
FixDelete or correct the duplicate. If you meant one branch to cover both values, put them on one line with a comma: 2, 3 -> ....
e: Expecting an element
MeansUsually a missing or extra curly bracket, so Kotlin has lost track of where the when block ends.
FixCount the brackets from the top of the function. Tapping next to a bracket in Pocket Studio highlights its partner, which finds the odd one out quickly.
Recap
  • 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 = — a when must be exhaustive, which in practice means it needs an else.
  • With no value in the round brackets, each branch becomes its own — the tidy replacement for a long else if chain.
  • Next: repeating things. while, for, ranges, and the counting mistake that has shipped in more software than any other.