Kotlin - Control Flow (if/else/when)
Kotlin Control Flow (if/else/when)
Control flow statements determine the execution path of your program. Learn Kotlin's powerful if expressions, when statements, and pattern matching capabilities.
if Expressions
Key Difference: In Kotlin, if is an expression (returns a value), not just a statement like in many other languages.
Basic if Expression
val age = 18
// Traditional if statement
if (age >= 18) {
println("You are an adult")
} else {
println("You are a minor")
}
// if as an expression (returns a value)
val status = if (age >= 18) "adult" else "minor"
println("You are an $status")
// Multi-line if expression
val message = if (age >= 18) {
println("Checking adult privileges...")
"You can vote and drive"
} else {
println("Checking minor restrictions...")
"Ask your parents for permission"
}
if-else if Chains
fun getGrade(score: Int): String {
return if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else if (score >= 70) {
"C"
} else if (score >= 60) {
"D"
} else {
"F"
}
}
// Usage
val score = 85
val grade = getGrade(score)
println("Score: $score, Grade: $grade") // Score: 85, Grade: B
Complex Conditions
fun checkWeather(temperature: Int, isRaining: Boolean, windSpeed: Int): String {
return if (temperature > 25 && !isRaining && windSpeed < 15) {
"Perfect weather for outdoor activities!"
} else if (temperature < 0 || windSpeed > 50) {
"Stay indoors - dangerous weather!"
} else if (isRaining) {
"Don't forget your umbrella!"
} else {
"Weather is okay, dress appropriately"
}
}
// Examples
println(checkWeather(28, false, 10)) // Perfect weather for outdoor activities!
println(checkWeather(15, true, 20)) // Don't forget your umbrella!
println(checkWeather(-5, false, 30)) // Stay indoors - dangerous weather!
when Expressions
The when expression is Kotlin's replacement for switch statements, but much more powerful.
Basic when Expression
fun describeDayOfWeek(day: Int): String {
return when (day) {
1 -> "Monday - Start of work week"
2 -> "Tuesday - Still getting into the groove"
3 -> "Wednesday - Hump day!"
4 -> "Thursday - Almost there"
5 -> "Friday - TGIF!"
6, 7 -> "Weekend - Time to relax!"
else -> "Invalid day"
}
}
// Usage
for (day in 1..8) {
println("Day $day: ${describeDayOfWeek(day)}")
}
when with Ranges and Collections
fun categorizeAge(age: Int): String {
return when (age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
in 20..64 -> "Adult"
in 65..120 -> "Senior"
else -> "Invalid age"
}
}
fun checkTestScore(score: Int): String {
return when (score) {
in 90..100 -> "Excellent! ๐"
in 80..89 -> "Good job! ๐"
in 70..79 -> "Not bad ๐"
in 60..69 -> "Needs improvement ๐"
in 0..59 -> "Please study more ๐"
else -> "Invalid score"
}
}
// Examples
println(categorizeAge(16)) // Teenager
println(checkTestScore(92)) // Excellent! ๐
when with Type Checking
fun processValue(value: Any): String {
return when (value) {
is String -> "Text: '$value' (length: ${value.length})"
is Int -> "Integer: $value (doubled: ${value * 2})"
is Double -> "Decimal: $value (rounded: ${value.toInt()})"
is Boolean -> "Boolean: $value (opposite: ${!value})"
is List<*> -> "List with ${value.size} items"
null -> "This value is null"
else -> "Unknown type: ${value::class.simpleName}"
}
}
// Examples
println(processValue("Hello")) // Text: 'Hello' (length: 5)
println(processValue(42)) // Integer: 42 (doubled: 84)
println(processValue(3.14)) // Decimal: 3.14 (rounded: 3)
println(processValue(listOf(1, 2, 3))) // List with 3 items
println(processValue(null)) // This value is null
when with Conditions
fun analyzeNumber(x: Int): String {
return when {
x < 0 -> "Negative number"
x == 0 -> "Zero"
x % 2 == 0 -> "Positive even number"
x % 2 == 1 -> "Positive odd number"
else -> "This shouldn't happen"
}
}
fun checkPasswordStrength(password: String): String {
return when {
password.length < 6 -> "Too short - minimum 6 characters"
password.length > 20 -> "Too long - maximum 20 characters"
!password.any { it.isUpperCase() } -> "Add at least one uppercase letter"
!password.any { it.isLowerCase() } -> "Add at least one lowercase letter"
!password.any { it.isDigit() } -> "Add at least one number"
password.all { it.isLetterOrDigit() } -> "Add special characters"
else -> "Strong password! โ
"
}
}
// Examples
println(analyzeNumber(-5)) // Negative number
println(analyzeNumber(8)) // Positive even number
println(checkPasswordStrength("MyPassword123!")) // Strong password! โ
when as Statement vs Expression
// when as a statement (doesn't return value)
val dayType = 3
when (dayType) {
1, 2, 3, 4, 5 -> println("Weekday")
6, 7 -> println("Weekend")
}
// when as an expression (returns value)
val dayDescription = when (dayType) {
1, 2, 3, 4, 5 -> "Time to work"
6, 7 -> "Time to relax"
else -> "Invalid day"
}
// when with multiple statements in branches
val result = when (dayType) {
in 1..5 -> {
println("It's a weekday")
"Work day"
}
in 6..7 -> {
println("It's weekend")
"Rest day"
}
else -> {
println("Invalid day")
"Unknown"
}
}
Pattern Matching with Sealed Classes
Exhaustive when with Sealed Classes
sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
object Empty : Result()
fun handleResult(result: Result): String {
// No 'else' needed - when is exhaustive for sealed classes
return when (result) {
is Success -> "Got data: ${result.data}"
is Error -> "Error occurred: ${result.message}"
is Loading -> "Loading..."
is Empty -> "No data available"
}
}
// Usage
val results = listOf(
Success("User data loaded"),
Error("Network timeout"),
Loading,
Empty
)
results.forEach { result ->
println(handleResult(result))
}
Advanced Control Flow Patterns
Nested when Expressions
enum class UserRole { ADMIN, MODERATOR, USER, GUEST }
enum class Permission { READ, WRITE, DELETE, MANAGE }
fun checkPermission(role: UserRole, permission: Permission): Boolean {
return when (role) {
UserRole.ADMIN -> true // Admin has all permissions
UserRole.MODERATOR -> when (permission) {
Permission.READ, Permission.WRITE, Permission.DELETE -> true
Permission.MANAGE -> false
}
UserRole.USER -> when (permission) {
Permission.READ, Permission.WRITE -> true
Permission.DELETE, Permission.MANAGE -> false
}
UserRole.GUEST -> when (permission) {
Permission.READ -> true
else -> false
}
}
}
// Examples
println(checkPermission(UserRole.USER, Permission.READ)) // true
println(checkPermission(UserRole.USER, Permission.DELETE)) // false
println(checkPermission(UserRole.ADMIN, Permission.MANAGE)) // true
when with Custom Conditions
data class Student(val name: String, val grade: Double, val attendance: Double)
fun evaluateStudent(student: Student): String {
return when {
student.grade >= 95 && student.attendance >= 95 ->
"${student.name}: Outstanding performance! ๐"
student.grade >= 85 && student.attendance >= 90 ->
"${student.name}: Excellent work! โญ"
student.grade >= 75 && student.attendance >= 80 ->
"${student.name}: Good job! ๐"
student.grade >= 60 && student.attendance >= 70 ->
"${student.name}: Needs improvement ๐"
student.attendance < 70 ->
"${student.name}: Poor attendance - please attend regularly ๐
"
else ->
"${student.name}: Academic support needed ๐"
}
}
val students = listOf(
Student("Alice", 92.0, 96.0),
Student("Bob", 78.0, 82.0),
Student("Charlie", 65.0, 60.0)
)
students.forEach { student ->
println(evaluateStudent(student))
}
Real-World Examples
HTTP Status Code Handler
fun handleHttpResponse(statusCode: Int, body: String?): String {
return when (statusCode) {
200 -> "Success: ${body ?: "No content"}"
201 -> "Created successfully"
204 -> "No content"
in 300..399 -> "Redirection: Check new location"
400 -> "Bad request: Check your data"
401 -> "Unauthorized: Please login"
403 -> "Forbidden: Access denied"
404 -> "Not found: Resource doesn't exist"
in 400..499 -> "Client error: $statusCode"
500 -> "Internal server error"
502 -> "Bad gateway"
503 -> "Service unavailable"
in 500..599 -> "Server error: $statusCode"
else -> "Unexpected status code: $statusCode"
}
}
// Simulate API responses
val responses = listOf(
200 to "User data",
404 to null,
500 to null,
201 to "User created"
)
responses.forEach { (status, data) ->
println("$status: ${handleHttpResponse(status, data)}")
}
File Processing System
enum class FileType { TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, UNKNOWN }
fun getFileType(extension: String): FileType {
return when (extension.lowercase()) {
"txt", "md", "log" -> FileType.TEXT
"jpg", "jpeg", "png", "gif", "bmp" -> FileType.IMAGE
"mp4", "avi", "mov", "mkv" -> FileType.VIDEO
"mp3", "wav", "flac", "aac" -> FileType.AUDIO
"pdf", "doc", "docx", "ppt", "pptx" -> FileType.DOCUMENT
else -> FileType.UNKNOWN
}
}
fun processFile(filename: String): String {
val extension = filename.substringAfterLast('.', "")
val fileType = getFileType(extension)
return when (fileType) {
FileType.TEXT -> "Processing text file: $filename"
FileType.IMAGE -> "Generating thumbnail for: $filename"
FileType.VIDEO -> "Encoding video: $filename"
FileType.AUDIO -> "Extracting metadata from: $filename"
FileType.DOCUMENT -> "Converting document: $filename"
FileType.UNKNOWN -> "Unknown file type: $filename"
}
}
val files = listOf(
"document.pdf",
"photo.jpg",
"music.mp3",
"video.mp4",
"readme.txt",
"mystery.xyz"
)
files.forEach { filename ->
println(processFile(filename))
}
Game State Manager
sealed class GameState
object MainMenu : GameState()
object Loading : GameState()
data class Playing(val level: Int, val score: Int) : GameState()
data class Paused(val level: Int, val score: Int) : GameState()
data class GameOver(val finalScore: Int, val highScore: Boolean) : GameState()
class Game {
private var currentState: GameState = MainMenu
fun handleInput(input: String): String {
return when (currentState) {
is MainMenu -> when (input.lowercase()) {
"start" -> {
currentState = Loading
"Starting game..."
}
"quit" -> "Thanks for playing!"
else -> "Commands: start, quit"
}
is Loading -> "Loading... Please wait"
is Playing -> when (input.lowercase()) {
"pause" -> {
currentState = Paused(currentState.level, currentState.score)
"Game paused"
}
"quit" -> {
currentState = GameOver(currentState.score, false)
"Game ended. Final score: ${currentState.score}"
}
else -> "Playing level ${currentState.level}, score: ${currentState.score}"
}
is Paused -> when (input.lowercase()) {
"resume" -> {
currentState = Playing(currentState.level, currentState.score)
"Game resumed"
}
"quit" -> {
currentState = GameOver(currentState.score, false)
"Game ended"
}
else -> "Commands: resume, quit"
}
is GameOver -> when (input.lowercase()) {
"restart" -> {
currentState = MainMenu
"Returning to main menu"
}
else -> "Game over! Final score: ${currentState.finalScore}. Type 'restart'"
}
}
}
}
// Demo
val game = Game()
val inputs = listOf("start", "pause", "resume", "quit", "restart")
inputs.forEach { input ->
println("Input: $input -> ${game.handleInput(input)}")
}
Best Practices
โ Good Practices
- Use when instead of long if-else chains
- Leverage when's exhaustiveness with sealed classes
- Use ranges in when expressions for numeric conditions
- Prefer expressions over statements when you need return values
- Use smart casts with type checking in when
โ Avoid
- Complex nested conditions that are hard to read
- Missing else clauses in when expressions
- Using multiple conditions when when would be clearer
- Deeply nested if-else structures
Architecture Note: Kotlin's control flow constructs are expressions that return values, enabling more functional programming patterns. The when expression is particularly powerful for pattern matching and handling complex conditional logic.
Practice Exercises
- Create a calculator that uses when expressions for different operations
- Build a grade calculator that categorizes scores using ranges
- Design a simple chatbot that responds based on user input patterns
- Implement a traffic light system using sealed classes and when
- Create a file processor that handles different file types
Quick Quiz
- What's the main difference between if in Kotlin vs other languages?
- When do you not need an else clause in a when expression?
- How do you check for multiple values in a when branch?
- What's the advantage of using sealed classes with when?
Show answers
- In Kotlin, if is an expression that returns a value, not just a statement
- When the when expression is exhaustive (covers all possible cases), like with sealed classes or enums
- Use comma-separated values: when(x) { 1, 2, 3 -> "one to three" }
- The compiler ensures exhaustiveness - you can't miss a case, making code more reliable