When using when
expressions over enum
or sealed hierarchy (sealed interface
or sealed class
), the Kotlin compiler provides, by default, exhaustiveness checks. In other words, the compiler verifies that all possible values are handled in the when
expression.
enum class ChairType { KitchenChair, OfficeChair, } fun sitOn(chairType: ChairType) = when (chairType) { KitchenChair -> println("sitting slightly comfortable") OfficeChair -> println("sitting very comfortable") }
Adding a new type RelaxingChair
without handling the new value will result in a compilation error:
enum class ChairType { KitchenChair, OfficeChair, RelaxingChair, } fun sitOn(chairType: ChairType) = when (chairType) { <--- compilation error: 'when' expression must be exhaustive, add necessary 'RelaxingChair' branch or 'else' branch instead KitchenChair -> println("sitting slightly comfortable") OfficeChair -> println("sitting very comfortable") }
The error message suggests two approaches: adding the missing branches for the new values or adding the else
branch.
The problem with adding the else
branch is that we are losing the exhaustiveness checks, so next time we add a new value we won’t get an error.
enum class ChairType { KitchenChair, OfficeChair, RelaxingChair, } fun sitOn(chairType: ChairType) = when (chairType) { KitchenChair -> println("sitting slightly comfortable") OfficeChair -> println("sitting very comfortable") else -> println("sitting on something else: $chairType") }
Adding a new type, ReclinedChair
won’t give a compilation error.
enum class ChairType { KitchenChair, OfficeChair, RelaxingChair, ReclinedChair, } fun sitOn(chairType: ChairType) = when (chairType) { KitchenChair -> println("sitting slightly comfortable") OfficeChair -> println("sitting very comfortable") else -> println("sitting on something else: $chairType") }
The most important advantage of having exhaustive checks is that the compiler guarantees all possible values are handled. In practice, the compiler errors will point you to all the places that might require some code. In a big codebase, it can make a big difference.
enum class ChairType { KitchenChair, OfficeChair, RelaxingChair, ReclinedChair, } fun sitOn(chairType: ChairType) = when (chairType) { KitchenChair -> println("sitting comfortable") OfficeChair -> println("sitting very comfortable") RelaxingChair -> println("sitting on clouds") ReclinedChair -> println("sitting reclined, very comfortable") }
There are cases in which we have no requirement to handle additional cases. One potential solution could be to tell the function to do nothing when it encounters the types that don’t need handling:
fun sitOn(chairType: ChairType) = when (chairType) { KitchenChair -> println("sitting comfortable") OfficeChair -> println("sitting very comfortable") RelaxingChair, ReclinedChair, -> {} }
Another advantage of exhaustive checks is that the IDE will help you generate the code for the missing branches.
You should use else
in when
expressions over enum
or sealed hierarchy only when you are sure that new types should never be handled by you or anyone else.
My advice: never add the else
branch in when
expressions over enum
or sealed hierarchy. The effort is minimal compared to the benefits.