Kotlin When Expression

The kotlin when statement is a conditional expression that evaluates multiple branches sequentially until it finds a matching condition. Unlike Java’s switch statement, the when expression kotlin can work with any data type, supports complex conditions, and doesn’t require break statements.

Key Features of When Expression

The kotlin when expression offers several advantages over traditional switch statements:

  • Expression and Statement: Can be used both as an expression (returns a value) and as a statement (performs actions)
  • Type Safety: Works with any data type including custom classes, sealed classes, and nullable types
  • No Fall-through: Automatically exits after matching a condition without requiring break statements
  • Smart Casting: Automatically casts types when using is checks
  • Range Support: Can match against ranges and collections using in operator

Basic Kotlin When Expression Syntax

The fundamental kotlin when syntax follows this pattern:

when (variable) {  
    condition1 -> action1  
    condition2 -> action2  
    else -> defaultAction  
}  

Simple Value Matching

Here’s how to use when in kotlin for basic value matching:

fun getDayType(day: String): String {  
    return when (day) {  
        "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday"  
        "Saturday", "Sunday" -> "Weekend"  
        else -> "Invalid day"  
    }  
}  

This example demonstrates multiple values matching the same condition using comma separation.

When as Expression vs Statement

The kotlin when expression can function in two ways:

As Expression (returns value):

val result = when (score) {  
    in 90..100 -> "Excellent"  
    in 80..89 -> "Good"  
    in 70..79 -> "Average"  
    else -> "Needs Improvement"  
}  

As Statement (performs action):

when (userRole) {  
    "Admin" -> grantAdminAccess()  
    "User" -> grantUserAccess()  
    "Guest" -> grantGuestAccess()  
}  

When Expression Without Subject

The when expression kotlin can be used without a subject, acting as a replacement for if-else chains:

fun validatePassword(password: String) {  
    when {  
        password.length < 8 -> throw IllegalArgumentException("Password too short")  
        !password.any { it.isDigit() } -> throw IllegalArgumentException("Password must contain numbers")  
        !password.any { it.isUpperCase() } -> throw IllegalArgumentException("Password must contain uppercase")  
        password.contains(" ") -> throw IllegalArgumentException("Password cannot contain spaces")  
    }  
}  

This pattern is particularly useful for complex conditional logic where you need to check multiple boolean expressions.

Advanced When Expression Patterns

Range Checking with In Operator

The kotlin when expression supports range checking using the in operator:

fun categorizeAge(age: Int): String = when (age) {  
    in 0..12 -> "Child"  
    in 13..19 -> "Teenager"  
    in 20..64 -> "Adult"  
    in 65..120 -> "Senior"  
    else -> "Invalid age"  
}  

Collection Membership Testing

You can check if values exist in collections:

fun checkValidGrade(grade: Char): Boolean {  
    val validGrades = listOf('A', 'B', 'C', 'D', 'F')  
    return when (grade) {  
        in validGrades -> true  
        else -> false  
    }  
}  

Type Checking with Smart Casting

The when expression in kotlin provides automatic smart casting when using is checks:

fun processData(data: Any): String = when (data) {  
    is String -> "Text: ${data.uppercase()}" // data is automatically cast to String  
    is Int -> "Number: ${data * 2}" // data is automatically cast to Int  
    is List<*> -> "List with ${data.size} items" // data is automatically cast to List  
    is Boolean -> if (data) "True value" else "False value"  
    else -> "Unknown type: ${data::class.simpleName}"  
}  

When Expression with Sealed Classes

One of the most powerful features of kotlin when expression is its integration with sealed classes for exhaustive pattern matching:

sealed class NetworkResult<out T> {  
    data class Success<T>(val data: T) : NetworkResult<T>()  
    data class Error(val exception: Throwable) : NetworkResult<Nothing>()  
    object Loading : NetworkResult<Nothing>()  
}  
  
fun handleNetworkResult(result: NetworkResult<String>) = when (result) {  
    is NetworkResult.Success -> showData(result.data)  
    is NetworkResult.Error -> showError(result.exception.message ?: "Unknown error")  
    NetworkResult.Loading -> showLoadingIndicator()  
    // No else clause needed - compiler ensures exhaustiveness  
}  

Benefits of Sealed Classes with When

  • Compile-time Exhaustiveness: Compiler ensures all cases are handled
  • Type Safety: Each branch has access to specific properties
  • Maintainability: Adding new sealed class variants triggers compilation errors until handled

Guard Conditions in When Expression

Kotlin supports guard conditions in when expressions, allowing additional conditions after the primary match:

sealed class Animal {  
    data class Dog(val breed: String, val isTrained: Boolean) : Animal()  
    data class Cat(val isIndoor: Boolean, val age: Int) : Animal()  
}  
  
fun handleAnimal(animal: Animal) = when (animal) {  
    is Animal.Dog if animal.isTrained -> "Well-behaved ${animal.breed}"  
    is Animal.Dog -> "Untrained ${animal.breed} needs training"  
    is Animal.Cat if animal.isIndoor && animal.age < 2 -> "Young indoor kitten"  
    is Animal.Cat if animal.isIndoor -> "Indoor cat"  
    is Animal.Cat -> "Outdoor cat"  
}  

Complex When Expression Examples

Nested When Expressions

You can nest when expressions for complex decision trees:

fun calculateDiscount(customerType: String, orderAmount: Double): Double {  
    return when (customerType) {  
        "Premium" -> when {  
            orderAmount >= 1000 -> 0.20  
            orderAmount >= 500 -> 0.15  
            else -> 0.10  
        }  
        "Regular" -> when {  
            orderAmount >= 1000 -> 0.10  
            orderAmount >= 500 -> 0.05  
            else -> 0.0  
        }  
        "New" -> 0.05  
        else -> 0.0  
    }  
}  

When with Function Calls

Branch conditions can include function calls and complex expressions:

class UserValidator {  
    fun isValidEmail(email: String): Boolean = email.contains("@") && email.contains(".")  
    fun isValidAge(age: Int): Boolean = age in 13..120  
      
    fun validateUser(email: String, age: Int, role: String) = when {  
        !isValidEmail(email) -> "Invalid email format"  
        !isValidAge(age) -> "Invalid age"  
        role.isBlank() -> "Role cannot be empty"  
        role.length < 3 -> "Role too short"  
        else -> "Valid user"  
    }  
}  

When with Custom Objects

The kotlin when expression works seamlessly with custom objects:

data class UserPermission(val level: Int, val department: String)  
  
fun checkAccess(permission: UserPermission, resource: String): Boolean = when {  
    permission.level >= 10 -> true // Admin access  
    permission.level >= 5 && permission.department == "IT" && resource.startsWith("tech") -> true  
    permission.level >= 3 && resource.startsWith("public") -> true  
    permission.level >= 1 && resource == "basic-info" -> true  
    else -> false  
}  

When Expression with Lambdas and Higher-Order Functions

You can combine when expressions with lambda functions for functional programming patterns:

fun processUserActions(actions: List<String>) {  
    actions.forEach { action ->  
        when (action.lowercase()) {  
            "login" -> authenticateUser()  
            "logout" -> terminateSession()  
            "refresh" -> refreshUserData()  
            else -> logUnknownAction(action)  
        }  
    }  
}  
  
// Using when with filter and map  
fun categorizeNumbers(numbers: List<Int>): Map<String, List<Int>> {  
    return numbers.groupBy { number ->  
        when {  
            number < 0 -> "negative"  
            number == 0 -> "zero"  
            number % 2 == 0 -> "positive-even"  
            else -> "positive-odd"  
        }  
    }  
}  

Performance Considerations

The kotlin when expression is optimized by the compiler based on the type of conditions:

  • Constant Values: Compiled to efficient jump tables
  • Range Checks: Optimized for numeric ranges
  • Type Checks: Leverages JVM’s instanceof operations
  • Sealed Classes: Compiled to efficient branch structures
// Efficient constant matching - compiled to jump table  
fun getStatusCode(status: String) = when (status) {  
    "SUCCESS" -> 200  
    "NOT_FOUND" -> 404  
    "ERROR" -> 500  
    else -> 0  
}  
  
// Efficient range checking  
fun categorizeTemperature(temp: Int) = when (temp) {  
    in Int.MIN_VALUE..0 -> "Freezing"  
    in 1..20 -> "Cold"  
    in 21..30 -> "Moderate"  
    in 31..Int.MAX_VALUE -> "Hot"  
    else -> "Invalid"  
}  

Common Patterns and Use Cases

State Management in Android

sealed class UiState {  
    object Loading : UiState()  
    data class Success(val data: List<String>) : UiState()  
    data class Error(val message: String) : UiState()  
    object Empty : UiState()  
}  
  
@Composable  
fun UserListScreen(uiState: UiState) {  
    when (uiState) {  
        UiState.Loading -> LoadingIndicator()  
        is UiState.Success -> LazyColumn {  
            items(uiState.data) { item ->  
                Text(text = item)  
            }  
        }  
        is UiState.Error -> ErrorMessage(uiState.message)  
        UiState.Empty -> EmptyStateMessage()  
    }  
}  

API Response Handling

data class ApiResponse<T>(  
    val statusCode: Int,  
    val data: T?,  
    val error: String?  
)  
  
fun <T> handleApiResponse(response: ApiResponse<T>) = when (response.statusCode) {  
    in 200..299 -> Result.success(response.data!!)  
    401 -> Result.failure(Exception("Unauthorized"))  
    in 400..499 -> Result.failure(Exception("Client error: ${response.error}"))  
    in 500..599 -> Result.failure(Exception("Server error: ${response.error}"))  
    else -> Result.failure(Exception("Unknown error"))  
}  

Complete Working Example

Here’s a comprehensive example demonstrating multiple when expression features in a real-world Android context:

import kotlin.random.Random  
  
// Sealed class for different user types  
sealed class UserType {  
    data class Premium(val subscriptionLevel: Int) : UserType()  
    data class Regular(val registrationDays: Int) : UserType()  
    object Guest : UserType()  
    data class Admin(val permissions: List<String>) : UserType()  
}  
  
// Data class for app features  
data class AppFeature(val name: String, val requiresPremium: Boolean, val minimumLevel: Int = 1)  
  
class FeatureAccessManager {  
    private val features = listOf(  
        AppFeature("basic_chat", requiresPremium = false),  
        AppFeature("video_call", requiresPremium = true, minimumLevel = 2),  
        AppFeature("cloud_storage", requiresPremium = true, minimumLevel = 1),  
        AppFeature("advanced_analytics", requiresPremium = true, minimumLevel = 3),  
        AppFeature("admin_panel", requiresPremium = false) // Admin only  
    )  
      
    fun checkFeatureAccess(userType: UserType, featureName: String): AccessResult {  
        val feature = features.find { it.name == featureName }  
            ?: return AccessResult.FeatureNotFound  
              
        return when (userType) {  
            is UserType.Premium -> when {  
                feature.name == "admin_panel" -> AccessResult.Denied("Admin access required")  
                feature.requiresPremium && userType.subscriptionLevel >= feature.minimumLevel ->   
                    AccessResult.Granted  
                !feature.requiresPremium -> AccessResult.Granted  
                else -> AccessResult.Denied("Premium level ${feature.minimumLevel} required")  
            }  
              
            is UserType.Regular -> when {  
                feature.name == "admin_panel" -> AccessResult.Denied("Admin access required")  
                feature.requiresPremium -> AccessResult.Denied("Premium subscription required")  
                userType.registrationDays >= 7 -> AccessResult.Granted  
                else -> AccessResult.Denied("Account must be 7+ days old")  
            }  
              
            UserType.Guest -> when (feature.name) {  
                "basic_chat" -> AccessResult.GrantedWithLimits("10 messages per day")  
                else -> AccessResult.Denied("Registration required")  
            }  
              
            is UserType.Admin -> when {  
                userType.permissions.contains("ALL") -> AccessResult.Granted  
                userType.permissions.contains(feature.name) -> AccessResult.Granted  
                else -> AccessResult.Denied("Insufficient admin permissions")  
            }  
        }  
    }  
      
    fun generateWelcomeMessage(userType: UserType): String = when (userType) {  
        is UserType.Premium -> when (userType.subscriptionLevel) {  
            in 1..2 -> "Welcome, Premium user! Enjoy your enhanced features."  
            in 3..5 -> "Welcome, Premium Pro! Access to all premium features unlocked."  
            else -> "Welcome, Premium Elite! You have unlimited access."  
        }  
        is UserType.Regular -> "Welcome back! ${  
            when {  
                userType.registrationDays < 7 -> "Complete verification to unlock more features."  
                userType.registrationDays < 30 -> "You're doing great! Consider upgrading to Premium."  
                else -> "Thanks for being a loyal user! Special offers await."  
            }  
        }"  
        UserType.Guest -> "Welcome, Guest! Sign up to unlock amazing features."  
        is UserType.Admin -> "Welcome, Administrator. System status: ${getSystemStatus()}"  
    }  
      
    private fun getSystemStatus(): String = when (Random.nextInt(1, 4)) {  
        1 -> "All systems operational"  
        2 -> "Minor maintenance in progress"  
        else -> "System monitoring active"  
    }  
}  
  
// Sealed class for access results  
sealed class AccessResult {  
    object Granted : AccessResult()  
    data class GrantedWithLimits(val limitations: String) : AccessResult()  
    data class Denied(val reason: String) : AccessResult()  
    object FeatureNotFound : AccessResult()  
}  
  
// Usage example  
fun main() {  
    val accessManager = FeatureAccessManager()  
      
    // Test different user types  
    val users = listOf(  
        UserType.Premium(subscriptionLevel = 3),  
        UserType.Regular(registrationDays = 15),  
        UserType.Guest,  
        UserType.Admin(permissions = listOf("basic_chat", "video_call", "admin_panel"))  
    )  
      
    val testFeatures = listOf("basic_chat", "video_call", "cloud_storage", "admin_panel")  
      
    users.forEach { user ->  
        println("\n--- ${user::class.simpleName} ---")  
        println(accessManager.generateWelcomeMessage(user))  
          
        testFeatures.forEach { feature ->  
            val result = accessManager.checkFeatureAccess(user, feature)  
            val status = when (result) {  
                AccessResult.Granted -> "✅ GRANTED"  
                is AccessResult.GrantedWithLimits -> "⚠️ GRANTED (${result.limitations})"  
                is AccessResult.Denied -> "❌ DENIED (${result.reason})"  
                AccessResult.FeatureNotFound -> "❓ FEATURE NOT FOUND"  
            }  
            println("$feature: $status")  
        }  
    }  
}  

Output:

--- Premium ---  
Welcome, Premium Pro! Access to all premium features unlocked.  
basic_chat: ✅ GRANTED  
video_call: ✅ GRANTED  
cloud_storage: ✅ GRANTED  
admin_panel: ❌ DENIED (Admin access required)  
  
--- Regular ---  
Welcome back! You're doing great! Consider upgrading to Premium.  
basic_chat: ✅ GRANTED  
video_call: ❌ DENIED (Premium subscription required)  
cloud_storage: ❌ DENIED (Premium subscription required)  
admin_panel: ❌ DENIED (Admin access required)  
  
--- Guest ---  
Welcome, Guest! Sign up to unlock amazing features.  
basic_chat: ⚠️ GRANTED (10 messages per day)  
video_call: ❌ DENIED (Registration required)  
cloud_storage: ❌ DENIED (Registration required)  
admin_panel: ❌ DENIED (Registration required)  
  
--- Admin ---  
Welcome, Administrator. System status: All systems operational  
basic_chat: ✅ GRANTED  
video_call: ✅ GRANTED  
cloud_storage: ❌ DENIED (Insufficient admin permissions)  
admin_panel: ✅ GRANTED  

This comprehensive example demonstrates the kotlin when expression in action, showcasing its power for building robust, type-safe conditional logic in Android applications. The pattern matching capabilities, combined with sealed classes and smart casting, create clean, maintainable code that’s both expressive and performant.