
Mastering Kotlin's when Expression: Complete Guide with Examples
Discover how to write cleaner, more expressive code using Kotlin's powerful when expression. Learn everything from basic replacements for switch statements to advanced sealed class pattern matching with real-world examples
Kotlin's when
expression is one of the language's most powerful and flexible features, offering far more capabilities than a simple replacement for Java's switch statement. Whether you're coming from Java or starting fresh with Kotlin, understanding when
is crucial for writing idiomatic, expressive code. In this comprehensive guide, we'll explore everything from basic usage to advanced patterns, helping you master this essential Kotlin feature.
What You'll Learn
- How
when
improves upon Java's switch statement - Using
when
as both an expression and statement - Advanced pattern matching techniques
- Smart casting with
when
- Best practices and common pitfalls
- Real-world examples and practical applications
Estimated reading time: 20 minutes
The Basics: From switch to when
If you're coming from Java, you're probably familiar with the switch statement. Let's see how Kotlin's when
expression improves upon it:
// Java switch example
switch (day) {
case "MONDAY":
case "TUESDAY":
case "WEDNESDAY":
case "THURSDAY":
case "FRIDAY":
System.out.println("Weekday");
break;
case "SATURDAY":
case "SUNDAY":
System.out.println("Weekend");
break;
default:
System.out.println("Invalid day");
}
// Equivalent Kotlin when expression
when (day.uppercase()) {
"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> println("Weekday")
"SATURDAY", "SUNDAY" -> println("Weekend")
else -> println("Invalid day")
}
Key improvements in Kotlin's when
:
- No break statements needed - each branch is self-contained
- Multiple values can be specified in a single branch using commas
- The else branch replaces default
- Arrow syntax (
->
) clearly separates conditions from actions
When as an Expression
One of the most powerful aspects of when
is that it can be used as an expression, returning a value:
val temperature = 25
val description = when {
temperature < 0 -> "Freezing"
temperature < 10 -> "Cold"
temperature < 20 -> "Mild"
temperature < 30 -> "Warm"
else -> "Hot"
}
// The expression above replaces this more verbose if-else structure:
val description2 = if (temperature < 0) {
"Freezing"
} else if (temperature < 10) {
"Cold"
} else if (temperature < 20) {
"Mild"
} else if (temperature < 30) {
"Warm"
} else {
"Hot"
}
Advanced Pattern Matching
Type Checking and Smart Casting
The when
expression excels at type checking and automatically smart casts the checked value:
fun processValue(value: Any) = when (value) {
is String -> "Length of string: ${value.length}" // Smart cast to String
is Int -> "Square of number: ${value * value}" // Smart cast to Int
is List<*> -> "List size: ${value.size}" // Smart cast to List
else -> "Unknown type: ${value::class.simpleName}"
}
// Usage
println(processValue("Hello")) // "Length of string: 5"
println(processValue(4)) // "Square of number: 16"
println(processValue(listOf(1,2))) // "List size: 2"
Range and Collection Checks
when
can check if a value is within ranges or collections:
fun classifyNumber(num: Int) = when (num) {
in 1..10 -> "Single digit"
in 11..99 -> "Two digits"
in 100..999 -> "Three digits"
else -> "Four or more digits"
}
fun processSpecialValue(value: Int) = when (value) {
in listOf(1, 3, 5, 7, 9) -> "Odd single digit"
in arrayOf(2, 4, 6, 8) -> "Even single digit"
!in 1..10 -> "Not a single digit"
else -> "Unknown classification"
}
Using when Without Arguments
when
without arguments is particularly powerful for complex conditional logic:
data class User(
val name: String,
val age: Int,
val email: String
)
fun validateUser(user: User) {
when {
user.name.isBlank() -> throw IllegalArgumentException("Name cannot be blank")
user.age < 0 -> throw IllegalArgumentException("Age cannot be negative")
!user.email.contains("@") -> throw IllegalArgumentException("Invalid email format")
user.email.isBlank() -> throw IllegalArgumentException("Email cannot be blank")
}
}
Sealed Classes and when
One of the most powerful combinations in Kotlin is using when
with sealed classes. This creates exhaustive pattern matching that the compiler can verify:
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
fun <T> handleResult(result: Result<T>) = when (result) {
is Result.Success -> displayData(result.data)
is Result.Error -> showError(result.message)
Result.Loading -> showLoadingIndicator()
// No else branch needed - compiler knows all cases are covered
}
Real-World Example: Expression Evaluator
Let's build a simple arithmetic expression evaluator using when
:
sealed class Expr {
data class Num(val value: Double) : Expr()
data class Sum(val left: Expr, val right: Expr) : Expr()
data class Multiply(val left: Expr, val right: Expr) : Expr()
data class Divide(val left: Expr, val right: Expr) : Expr()
}
fun eval(expr: Expr): Double = when (expr) {
is Expr.Num -> expr.value
is Expr.Sum -> eval(expr.left) + eval(expr.right)
is Expr.Multiply -> eval(expr.left) * eval(expr.right)
is Expr.Divide -> eval(expr.left) / eval(expr.right)
}
// Usage
val expression = Expr.Sum(
Expr.Num(5.0),
Expr.Multiply(
Expr.Num(2.0),
Expr.Num(3.0)
)
)
println(eval(expression)) // Outputs: 11.0
Best Practices
- Keep Conditions Simple
Instead of complex conditions in when branches, extract them to properties or functions:
// Avoid
when {
list.isNotEmpty() && list.size < 10 && list.all { it > 0 } -> process(list)
else -> handleError()
}
// Better
val isValidList = list.isNotEmpty() && list.size < 10
val hasOnlyPositiveNumbers = list.all { it > 0 }
when {
isValidList && hasOnlyPositiveNumbers -> process(list)
else -> handleError()
}
- Use Exhaustive When
When working with sealed classes or enums, make your when expressions exhaustive to catch all cases:
enum class Status { SUCCESS, ERROR, LOADING }
// Compiler error: 'when' expression must be exhaustive
fun handleStatus(status: Status) = when (status) {
Status.SUCCESS -> processSuccess()
Status.ERROR -> processError()
// Missing Status.LOADING case
}
- Leverage Smart Casting
Take advantage of smart casting to avoid unnecessary type checks:
sealed class Vehicle {
data class Car(val model: String, val year: Int) : Vehicle()
data class Motorcycle(val make: String, val cc: Int) : Vehicle()
}
fun describeVehicle(vehicle: Vehicle) = when (vehicle) {
is Vehicle.Car -> "${vehicle.model} (${vehicle.year})" // Smart cast to Car
is Vehicle.Motorcycle -> "${vehicle.make} ${vehicle.cc}cc" // Smart cast to Motorcycle
}
Common Pitfalls
- Forgetting the Else Branch
When usingwhen
as an expression with non-exhaustive conditions, you must provide an else branch:
// Compiler error
fun numberType(num: Number) = when (num) {
is Int -> "Integer"
is Double -> "Double"
// Missing else branch
}
// Correct
fun numberType(num: Number) = when (num) {
is Int -> "Integer"
is Double -> "Double"
else -> "Other number type"
}
- Overcomplicating Conditions
Whilewhen
is powerful, overcomplicating conditions can make code hard to maintain:
// Avoid
when {
value is String && value.length > 5 && value.startsWith("prefix") -> process(value)
value is Int && value > 0 && value % 2 == 0 -> process(value)
else -> handleDefault()
}
// Better
fun isValidString(value: String) = value.length > 5 && value.startsWith("prefix")
fun isValidNumber(value: Int) = value > 0 && value % 2 == 0
when {
value is String && isValidString(value) -> process(value)
value is Int && isValidNumber(value) -> process(value)
else -> handleDefault()
}
Conclusion
Kotlin's when
expression is a versatile and powerful feature that goes far beyond being a simple switch replacement. By understanding its capabilities and following best practices, you can write more expressive, maintainable, and safer code. Whether you're doing simple value matching or complex pattern matching with sealed classes, when
provides a clean and powerful way to express your program's logic.
Remember these key points:
- Use
when
as an expression when possible to leverage its value-returning capability - Take advantage of smart casting with type checks
- Use sealed classes with
when
for compile-time exhaustiveness checking - Keep conditions simple and readable
- Extract complex logic into well-named functions or properties
Further Reading
To deepen your understanding of Kotlin's when
expression and related concepts, consider exploring:
- Sealed classes and interfaces
- Pattern matching in functional programming
- Smart casting in Kotlin
- Kotlin's type system