Skip to main content

Understanding Kotlin Control Flow

1. Using If Expressions

Feature Description Example
If Expression Used for conditional logic, evaluates to a value.
val x = 10
val result = if (x > 5) "Greater" else "Lesser"
If-Else Chain Allows multiple conditions in sequence.
val score = 85
val grade = if (score >= 90) "A"
            else if (score >= 80) "B"
            else "C"
If Expression with Multiple Statements Use curly braces to group multiple statements.
val a = 5
val b = 10
val max = if (a > b) {
    println("a is greater")
    a
} else {
    println("b is greater")
    b
}

Example: Nested If Expressions

val x = 10
val y = 20
val result = if (x > 5) {
    if (y > 15) "Both conditions met"
    else "Only x condition met"
} else {
    "No conditions met"
}

2. Working with When Expressions

The when expression in Kotlin is a powerful control flow construct that serves as a replacement for switch statements in other languages. It evaluates an expression and matches the result against multiple branches.

Feature Description Example
Basic Syntax Matches a value against multiple conditions
when (value) {
    1, 2, 3 -> println("Small number")
    else -> println("Large number")
}
Type Checking Can check types using is
when (obj) {
    is String -> println("String length: ${obj.length}")
    is Int -> println("Integer value: $obj")
}
Condition Matching Supports complex conditions with if
when {
    x > 0 && y > 0 -> println("First quadrant")
    x < 0 && y > 0 -> println("Second quadrant")
    else -> println("Other")
}
Return Value Can be used as an expression returning a value
val result = when (day) {
    "Monday", "Tuesday" -> "Weekday"
    else -> "Weekend"
}

Example: Using When with String Values

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

println(getDayType("Monday")) // Output: Weekday

Example: When with Type Checks

fun processValue(value: Any): String {
    return when (value) {
        is Int -> "Integer: $value"
        is String -> "String: $value"
        is Double -> "Double: $value"
        else -> "Unknown type"
    }
}

println(processValue(42)) // Output: Integer: 42
Note: Unlike switch in Java, Kotlin's when does not fall through to the next case. Each branch is evaluated independently.
Warning: If no matching branch is found and there's no else, the when expression will not compile if it's expected to return a value.

3. Implementing For Loops

Loop Type Description Example
for (item in collection) Iterates over items in a collection
val numbers = listOf(1, 2, 3)
for (number in numbers) {
    println(number)
}
for (index in range) Iterates over a numeric range
for (i in 1..5) {
    println(i)
}
for (index in collection.indices) Iterates over indices of a collection
val items = listOf("a", "b", "c")
for (i in items.indices) {
    println("$i: ${items[i]}")
}

Example: Looping with Index

val fruits = listOf("Apple", "Banana", "Cherry")
for ((index, fruit) in fruits.withIndex()) {
    println("Fruit $index is $fruit")
}
Note: The withIndex() function is useful for accessing both index and value during iteration.
Warning: Avoid modifying collections while iterating over them, as it may cause ConcurrentModificationException.

4. Using While and Do-While Loops

Loop Type Syntax Description Use Case
while while (condition) { ... } Evaluates condition before each iteration. Loop may not execute at all. When the number of iterations is unknown but depends on a condition.
do-while do { ... } while (condition) Executes the loop body at least once, then evaluates condition. When you want to execute code at least once before checking the condition.

Example: While Loop

var i = 0
while (i < 5) {
    println("Count: $i")
    i++
}

Example: Do-While Loop

var j = 0
do {
    println("Count: $j")
    j++
} while (j < 3)
Note: Be cautious with while loops to avoid infinite loops. Always ensure the loop condition will eventually become false.

Key Differences

  • While Loop: Checks condition before executing the loop body.
  • Do-While Loop: Executes the loop body at least once, then checks the condition.

5. Applying Break and Continue

Control Flow Description Usage
break Exits the innermost loop or switch statement for (i in 1..5) { if (i == 3) break; println(i) }
continue Skips the current iteration and continues with the next one for (i in 1..5) { if (i == 3) continue; println(i) }

Example: Using break in nested loops

outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) break@outer
        println("i = $i, j = $j")
    }
}

Example: Using continue in nested loops

outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) continue@outer
        println("i = $i, j = $j")
    }
}

6. Using Return with Labels (@label)

In Kotlin, return statements can be used with labels to control the flow of nested loops or functions. Labels allow you to specify which loop or function you want to return from, especially useful in nested structures.

Feature Description Example
Label Syntax Labels are defined using the @ symbol before a name.
loop@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2) break@loop
    }
}
Return with Label Use return@label to return from a specific labeled block.
fun processList(items: List<Int>): Int {
    outer@ for (item in items) {
        if (item < 0) return@outer -1
    }
    return 0
}

Example: Using Labels to Break Nested Loops

fun findFirstPositive(): Int? {
    outerLoop@ for (i in 1..3) {
        for (j in 1..3) {
            if (i * j > 5) {
                break@outerLoop
            }
            println("i=$i, j=$j")
        }
    }
    return null
}

Example: Using Labels with Return

fun processMatrix(matrix: Array<Array<Int>>): Int {
    outer@ for (i in matrix.indices) {
        for (j in matrix[i].indices) {
            if (matrix[i][j] < 0) {
                return@outer -1
            }
        }
    }
    return 0
}

7. Implementing Try-Catch as Expression

Construct Description Usage
try-catch as expression Allows try-catch blocks to be used as expressions, returning a value.
val result = try {
    val value = riskyOperation()
    value
} catch (e: Exception) {
    defaultValue
}

Example: Try-Catch Expression

fun parseStringToInt(input: String): Int {
    return try {
        input.toInt()
    } catch (e: NumberFormatException) {
        0
    }
}

val result = parseStringToInt("123") // Returns 123
val fallback = parseStringToInt("abc") // Returns 0
Note: The try-catch block must have a return type that matches the expected value, and all branches must return the same type.

8. Using Safe Calls and Elvis Operator (?., ?:)

The safe call operator (?) and the Elvis operator (?:) are essential Kotlin features for handling null values safely.

Operator Description Example Result
? Safe call operator. Calls a method or accesses a property only if the receiver is not null. val name = person?.name If person is null, name will be null. Otherwise, it will hold the value of person.name.
?: Elvis operator. Provides a default value if the expression on its left is null. val name = person?.name ?: "Unknown" If person is null or name is null, name will be "Unknown".

Example: Safe Calls in a Function

fun getPersonName(person: Person?): String {
    return person?.name ?: "No Name"
}

// Usage
val person: Person? = null
println(getPersonName(person)) // Output: No Name

Example: Chaining Safe Calls

val address = person?.address?.street ?: "No Address"

// Equivalent to:
// val address = if (person != null && person.address != null) {
//     person.address.street
// } else {
//     "No Address"
// }

9. Working with Not-Null Assertion (!!)

The not-null assertion operator (!!) is used to explicitly assert that a value is not null in Kotlin. It forces the compiler to treat a nullable type as non-nullable, and will throw a NullPointerException at runtime if the value is indeed null.

Operator Description Example
!! Asserts that the value is not null and throws an exception if it is
val nullableString: String? = null
val nonNullString: String = nullableString!!

Example: Using !! to Force Non-Null Value

val name: String? = "Kotlin"
println(name!!.length) // Outputs: 5

Example: Runtime Exception with !!

val nullableValue: Int? = null
val result = nullableValue!! // Throws NullPointerException
Note: Using !! should be avoided in most cases as it can lead to runtime exceptions. Prefer safe calls (?) or if checks for safer handling.

10. Implementing Smart Casts

Smart casts in Kotlin allow you to safely cast a variable to a more specific type without explicitly checking or casting it. The compiler automatically handles the type check and cast when it can determine that a variable is of a specific type at runtime.

Condition Description Example
Type check with is Checks if a variable is of a specific type and enables smart cast
val obj: Any = "Hello"
if (obj is String) {
    println(obj.length) // Smart cast to String
}
Type check with as Explicit cast, but only if type is confirmed
val obj: Any = "Hello"
val str = obj as String // Safe cast if obj is String
Safe cast with as? Safely casts and returns null if cast fails
val obj: Any = 42
val str = obj as? String // Returns null

Example: Smart Cast in Conditional

val value: Any = "Kotlin"
if (value is String) {
    println("Length: ${value.length}") // Smart cast to String
}

Example: Using when with Smart Casts

val items: List<Any> = listOf("Hello", 42, true)
items.forEach {
    when (it) {
        is String -> println("String: $it")
        is Int -> println("Int: $it")
        is Boolean -> println("Boolean: $it")
    }
}
Note: Smart casts only work in local scope and within conditional blocks where the compiler can statically verify type safety.

Was this article helpful?