Control Flow & when

Reviewed & published by Brayan K

Kotlin is a modern, concise language where decisions are made with if expressions and the powerful when construct — a supercharged replacement for the switch statement.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll use if as an expression (no ternary needed) and wield when with values, ranges, conditions, and type checks.

What You'll Learn in This Lesson

1️⃣ if as a Statement and an Expression

if/else chooses between paths just like in any language. The Kotlin twist is that if is also an expression — it evaluates to a value you can assign or return. That's why Kotlin has no separate ternary operator.

fun main() {
    val temperature = 18

    // if/else works like everywhere else...
    if (temperature > 25) {
        println("Hot")
    } else if (temperature > 15) {
        println("Mild")
    } else {
        println("Cold")
    }

    // ...but in Kotlin, if is an EXPRESSION that returns a value.
    // No ternary operator needed:
    val label = if (temperature > 20) "warm" else "cool"
    println("It feels $label")          // -> It feels cool

    // Because it returns a value, you can use it inline:
    val max = if (3 > 5) 3 else 5
    println("Max is $max")               // -> Max is 5
}

The line val label = if (...) "warm" else "cool" replaces the cond ? a : b you might know from other languages — clearer and using a keyword you already understand.

2️⃣ The when Expression

when is Kotlin's answer to the switch statement, but far more capable. It can match exact values, several values per branch, ranges with in, arbitrary boolean conditions, and types with is. Used as an expression it returns a value, and then an else branch is required to cover every case.

fun main() {
    val day = 6

    // when replaces long if/else chains and switch statements.
    val name = when (day) {
        1 -> "Monday"
        2, 3, 4, 5 -> "Midweek"      // multiple values per branch
        6, 7 -> "Weekend"
        else -> "Unknown"            // else is required when used as an expression
    }
    println(name)                    // -> Weekend

    // when with ranges and conditions (no argument needed):
    val score = 84
    val grade = when {
        score >= 90 -> "A"
        score in 80..89 -> "B"       // range check with 'in'
        score in 70..79 -> "C"
        else -> "F"
    }
    println("Grade: $grade")          // -> Grade: B

    // when can also smart-cast on type:
    val item: Any = "hello"
    when (item) {
        is Int -> println("an Int")
        is String -> println("a String of length ${item.length}")
        else -> println("something else")
    }
}

Notice the third block: after is String -> the compiler smart-casts item to String, so item.length just works.

Your turn. Fill in the TODO, then run and compare.

// 🎯 YOUR TURN — replace each TODO then run main()
fun main() {
    val hour = 14

    // 1) Use a 'when' WITHOUT an argument to pick a greeting:
    //    before 12 -> "Good morning", before 18 -> "Good afternoon",
    //    otherwise -> "Good evening"
    val greeting = when {
        hour < 12 -> "Good morning"
        hour < 18 -> "TODO"          // 👉 put "Good afternoon"
        else -> "Good evening"
    }

    println(greeting)

    // ✅ Expected (hour = 14):  Good afternoon
}

Mini-Challenge: A Traffic-Light Advisor

Use a when expression to map a light colour to advice. Remember the else branch.

// 🎯 MINI-CHALLENGE: a traffic-light advisor.
// Save as Light.kt and run main().
//
// 1. Set light = "yellow".
// 2. Use a 'when (light)' expression to map:
//    "red" -> "Stop", "yellow" -> "Slow down",
//    "green" -> "Go", else -> "Unknown signal".
// 3. Store the result in a val and print "Advice: <result>".
//
// ✅ Example output (light = "yellow"):
//    Advice: Slow down

fun main() {
    val light = "yellow"
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Control Flow

ConceptKotlin Syntax
if expressionval x = if (c) a else b
when on a valuewhen (n) { 1 -> ... }
Multiple values2, 3 -> "..."
Range branchin 1..9 -> "..."
Type branchis String -> ...
Catch-allelse -> "..."

🎉 Lesson Complete!

Practice quiz

Why does Kotlin have no ternary operator (a ? b : c)?

  • Because it lacks if/else
  • Because ternaries are banned
  • Because if is already an expression that returns a value
  • Because when replaces if

Answer: Because if is already an expression that returns a value. if (cond) a else b returns a value, so a ternary would be redundant.

What does val label = if (t > 20) "warm" else "cool" demonstrate?

  • if used as an expression returning a value
  • A syntax error
  • A loop
  • A type cast

Answer: if used as an expression returning a value. if is an expression, so its result can be assigned to a variable.

What is the arrow used in each when branch?

  • :
  • =>
  • ==
  • ->

Answer: ->. Kotlin when branches use condition -> result.

How do you match several values in one when branch?

  • Repeat the branch
  • Separate them with commas, like 2, 3, 4 ->
  • Use && between them
  • It is impossible

Answer: Separate them with commas, like 2, 3, 4 ->. List the values separated by commas: 2, 3, 4 -> ...

Which when branch checks a range?

  • score in 80..89 ->
  • score == 80..89 ->
  • score has 80..89 ->
  • range(80, 89) ->

Answer: score in 80..89 ->. Use the in operator with a range: score in 80..89 ->.

When is an else branch required in a when?

  • Never
  • Always
  • Only with strings
  • When when is used as an expression and the branches are not exhaustive

Answer: When when is used as an expression and the branches are not exhaustive. An expression must produce a value for every input, so a non-exhaustive when needs else.

Does a Kotlin when need break statements like a switch?

  • Yes, after every branch
  • No, only the matching branch runs, no break needed
  • Only for the else branch
  • Only inside loops

Answer: No, only the matching branch runs, no break needed. There is no fall-through; only the matched branch executes.

What happens after an is String -> branch in a when?

  • A ClassCastException
  • Nothing useful
  • The value smart-casts to String inside that branch
  • The branch is skipped

Answer: The value smart-casts to String inside that branch. A type branch smart-casts the value, so String members work directly.

Which keyword introduces a when branch that matches any remaining case?

  • else
  • default
  • otherwise
  • any

Answer: else. else is the catch-all branch in a when.

What does a when expression with no argument (when { ... }) let you write?

  • Full boolean conditions per branch, like an if/else-if chain
  • Only type checks
  • Only ranges
  • Nothing, it is invalid

Answer: Full boolean conditions per branch, like an if/else-if chain. Argument-free when evaluates a boolean condition in each branch.

Continue this course

Frequently asked questions

Why does Kotlin have no ternary operator (a ? b : c)?

Because Kotlin's if is already an expression that returns a value, a separate ternary operator would be redundant. Instead of cond ? a : b you simply write if (cond) a else b. This reads more clearly and uses the same keyword you already know. The same is true of when, which can also be used as an expression that produces a value.

When should I use when instead of if/else?

Reach for when whenever you are comparing one value against several possibilities, or when an if/else-if chain grows beyond two or three branches. when is more readable, supports matching multiple values per branch (2, 3 -> ...), ranges (in 1..10), type checks (is String), and arbitrary conditions. For a simple two-way decision, a plain if is fine.

Why does the compiler force an else branch sometimes?

When you use when (or if) as an expression — that is, when its result is assigned or returned — the compiler must be sure every possible input produces a value. If the branches don't cover all cases, you must add an else. The exception is when matching on an enum or sealed class where you have already listed every case; then the compiler knows it is exhaustive and else is optional.

What is smart casting inside a when block?

When a when branch checks the type of a value with is, Kotlin automatically treats the value as that type inside the branch — this is a smart cast. So after is String -> you can call String methods like item.length directly, with no explicit cast. The compiler proves the cast is safe, eliminating both boilerplate and ClassCastExceptions.