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

  1. Create a calculator that uses when expressions for different operations
  2. Build a grade calculator that categorizes scores using ranges
  3. Design a simple chatbot that responds based on user input patterns
  4. Implement a traffic light system using sealed classes and when
  5. Create a file processor that handles different file types

Quick Quiz

  1. What's the main difference between if in Kotlin vs other languages?
  2. When do you not need an else clause in a when expression?
  3. How do you check for multiple values in a when branch?
  4. What's the advantage of using sealed classes with when?
Show answers
  1. In Kotlin, if is an expression that returns a value, not just a statement
  2. When the when expression is exhaustive (covers all possible cases), like with sealed classes or enums
  3. Use comma-separated values: when(x) { 1, 2, 3 -> "one to three" }
  4. The compiler ensures exhaustiveness - you can't miss a case, making code more reliable