Skip to content
beginner Phase 1 · Kotlin Foundations

Control Flow

Use when expressions, for loops, ranges, and Kotlin's concise conditional syntax.

30m
2 problems
Topic Progress 0%

The When Expression

When replaces switch

Kotlin's when expression replaces Java's switch statement with a more powerful construct. It can match on values, types, ranges, and arbitrary predicates.

Basic Value Matching

val day = 3

val dayName = when (day) {
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"
    7 -> "Sunday"
    else -> "Invalid"
}
println(dayName)  // Wednesday

When used as an expression, it returns a value. The else branch is required when no branch matches.

Multiple Values and Ranges

Group values with commas. Check membership with in:

val score = 85

val grade = when (score) {
    in 90..100 -> "A"
    in 80 until 90 -> "B"
    in 70 until 80 -> "C"
    in 60 until 70 -> "D"
    else -> "F"
}

The in operator checks if a value falls within a range. until creates a half-open range (excludes the upper bound).

Type Checking

Use is to match on types. The compiler smart-casts the variable inside the branch:

fun describe(obj: Any): String {
    return when (obj) {
        is Int -> "Integer: $obj"
        is String -> "String of length ${obj.length}"
        is List<*> -> "List of ${obj.size} elements"
        else -> "Unknown type"
    }
}

The smart cast means obj is automatically treated as String inside the is String branch, so you can call .length without an explicit cast.

Without Argument

When used without an argument, each branch is a boolean condition. The first matching branch executes:

val temperature = 28

when {
    temperature >= 35 -> println("Extreme heat")
    temperature >= 25 -> println("Warm")
    temperature >= 15 -> println("Mild")
    else -> println("Cold")
}

This replaces chains of if-else-if with clearer syntax.

When as Statement

When can be used as a statement (no value returned) for side effects:

when (action) {
    "save" -> saveData()
    "delete" -> deleteData()
    "load" -> loadData()
    else -> println("Unknown action")
}

For Loops and Ranges

Ranges

Ranges in Kotlin are created with .. (inclusive) and until (exclusive). They implement the Iterable interface and work with for loops, in, and standard library functions:

// Inclusive range: 1, 2, 3, 4, 5
for (i in 1..5) {
    print("$i ")
}

// Exclusive range: 1, 2, 3, 4
for (i in 1 until 5) {
    print("$i ")
}

// Downward range: 5, 4, 3, 2, 1
for (i in 5 downTo 1) {
    print("$i ")
}

// Step: 1, 3, 5, 7, 9
for (i in 1..10 step 2) {
    print("$i ")
}

Iterating Collections

val fruits = listOf("apple", "banana", "cherry")

for (fruit in fruits) {
    println(fruit)
}

// With index
for ((index, fruit) in fruits.withIndex()) {
    println("$index: $fruit")
}

Destructuring in For Loops

Kotlin supports destructuring declarations in for loops. Any object with componentN() functions (including data classes and Map entries) can be destructured:

val capitals = mapOf(
    "USA" to "Washington",
    "Japan" to "Tokyo",
    "Germany" to "Berlin"
)

for ((country, city) in capitals) {
    println("$country -> $city")
}

// Data class destructuring
data class User(val name: String, val age: Int)
val users = listOf(User("Alice", 30), User("Bob", 25))

for ((name, age) in users) {
    println("$name is $age years old")
}

Break and Continue

Kotlin supports break and continue with labels for nested loops:

outer@ for (i in 1..5) {
    for (j in 1..5) {
        if (j == 3) continue@outer  // Skips to next i
        if (i == 4) break@outer     // Exits outer loop
        print("($i,$j) ")
    }
}
// Output: (1,1) (1,2) (2,1) (2,2) (3,1) (3,2)

Without labels, break and continue affect only the innermost loop.

If as an Expression

If Returns a Value

In Kotlin, if is an expression, not a statement. It returns a value, so you can use it where other languages require ternary operators:

val max = if (a > b) a else b

// With blocks
val result = if (score >= 90) {
    println("Excellent!")
    "A"
} else if (score >= 80) {
    "B"
} else {
    "C"
}

The last expression in each branch is the return value. This eliminates the need for ternary operators (which Kotlin does not have).

If vs When

Use when for three or more branches or pattern matching. Use if-else for simple two-branch decisions:

// Simple: if-else is fine
val abs = if (x >= 0) x else -x

// Complex: when is clearer
val description = when {
    x > 0 -> "positive"
    x < 0 -> "negative"
    else -> "zero"
}

Elvis with If

The Elvis operator often replaces simple if-null checks:

// Verbose
val length = if (name != null) name.length else 0

// Concise
val length = name?.length ?: 0

The Elvis operator is preferred because it is more concise and signals null handling intent clearly.

Practice Problems

0 / 3 solved
FizzBuzz with When

Write a function that prints numbers 1 to 100. For multiples of 3 print 'Fizz', for multiples of 5 print 'Buzz', for multiples of both print 'FizzBuzz', otherwise print the number. Use when expression.

Solution
fun fizzBuzz() {
    for (i in 1..100) {
        val result = when {
            i % 15 == 0 -> "FizzBuzz"
            i % 3 == 0 -> "Fizz"
            i % 5 == 0 -> "Buzz"
            else -> i.toString()
        }
        println(result)
    }
}
Range-Based Grade Calculator

Write a function that takes a score (0-100) and returns the letter grade using ranges: A (90-100), B (80-89), C (70-79), D (60-69), F (below 60). Handle invalid scores.

Solution
fun getGrade(score: Int): String {
    return when (score) {
        in 90..100 -> "A"
        in 80 until 90 -> "B"
        in 70 until 80 -> "C"
        in 60 until 70 -> "D"
        in 0 until 60 -> "F"
        else -> "Invalid score"
    }
}

fun main() {
    println(getGrade(95))   // A
    println(getGrade(82))   // B
    println(getGrade(67))   // D
    println(getGrade(45))   // F
    println(getGrade(105))  // Invalid score
}
Destructuring Map Processor

Given a map of product names to prices, iterate using destructuring and print each product. Then calculate the total price of all products priced above 50.

Solution
fun processProducts(products: Map<String, Double>) {
    for ((name, price) in products) {
        println("$name: $${price}")
    }

    val totalExpensive = products.values
        .filter { it > 50.0 }
        .sum()
    println("Total above $50: $${totalExpensive}")
}

fun main() {
    val products = mapOf(
        "Laptop" to 999.99,
        "Mouse" to 25.50,
        "Keyboard" to 75.00,
        "Monitor" to 350.00
    )
    processProducts(products)
}

Quiz

1. What makes Kotlin's when different from Java's switch?

Question 1 options

2. What does `1 until 5` produce?

Question 2 options

3. What does this code print? ```kotlin val x = 10 val result = if (x > 5) "big" else "small" println(result) ```

Question 3 options

4. How do you exit a nested for loop in Kotlin?

Question 4 options

Flashcards

Question

What is the difference between .. and until in Kotlin ranges?

Answer

.. creates an inclusive range (includes both endpoints). until creates a half-open range (excludes the upper bound). Example: 1..5 = 1,2,3,4,5; 1 until 5 = 1,2,3,4.

Question

How does smart casting work in when expressions?

Answer

When you use is to check a type, the compiler automatically casts the variable to that type within the branch. No explicit as cast needed. Example: is String lets you call .length directly.

Question

What is a labeled break in Kotlin?

Answer

A break with a label (break@outerName) exits a specific outer loop instead of just the innermost loop. Labels are defined with labelName@ before the loop.

Question

Why does Kotlin not have a ternary operator?

Answer

Because if is already an expression that returns a value. if (condition) valueA else valueB serves the same purpose without needing a separate operator.

Revision Notes

Key Takeaways

  • 1. when is more powerful than switch: it matches values, types, ranges, and conditions.
  • 2. if is an expression in Kotlin and returns a value.
  • 3. Ranges with .. are inclusive; until is exclusive.
  • 4. Destructuring in for loops makes Map iteration clean.
  • 5. Labeled break/continue control nested loops.

Interview Tips

  • Be ready to write when expressions for pattern matching problems.
  • Know the difference between .. (inclusive) and until (exclusive).
  • Practice destructuring data classes and Map entries in loops.
  • Explain why if-as-expression eliminates the need for ternary operators.

Cheat Sheet

Control Flow Cheat Sheet

When Expression:

  • Matches values: when (x) { 1 -> ... }
  • Matches types: when (x) { is String -> ... }
  • Matches ranges: when (x) { in 1..10 -> ... }
  • Boolean conditions: when { x > 0 -> ... }
  • Returns a value when used as expression

For Loops:

  • for (i in 1..5) — inclusive
  • for (i in 1 until 5) — exclusive
  • for (i in 5 downTo 1) — descending
  • for (i in 1..10 step 2) — with step
  • for ((k, v) in map) — destructuring

If as Expression:

  • val x = if (cond) a else b
  • Returns the last expression in each branch
  • Replaces ternary operator

Labels:

  • outer@ for (...) { break@outer }