Skip to content
Lesson 7 of 19

Step 1 of 5 · Reading · ~3 min

Learn

Control Flow

if and when

Kotlin has no ternary operator, and it does not miss one: if already produces a value.

fun main() {
    val score = 72
    val verdict = if (score >= 60) "pass" else "fail"
    println(verdict)
}

//> pass

That is the same if you already know, used in a position where a value is expected. The last expression in the chosen branch becomes the result.

The rule that catches everyone

An if used as an expression must have an else. An if used as a statement does not.

✓ Statement — no else needed, nothing is being produced:

if (score >= 60) println("pass")

✓ Expression — else required, because verdict has to end up holding something:

val verdict = if (score >= 60) "pass" else "fail"

✗ Does not compile — there is no value for the failing case:

val verdict = if (score >= 60) "pass"

when: a switch that grew up

when compares one subject against a list of branches. Unlike Java's switch, a branch can be a value, several values, a range, or a type check — and there is no fall-through, so there is no break to forget.

fun main() {
    val code = 404

    val meaning = when (code) {
        200, 201 -> "ok"
        in 300..399 -> "redirect"
        in 400..499 -> "client error"
        else -> "something else"
    }

    println(meaning)
}

//> client error
Branch formMatches
200 ->that exact value
200, 201 ->either value
in 400..499 ->anything inside the range
!in 400..499 ->anything outside the range
is String ->any value of that type, and smart-casts it
else ->everything left over

when with no subject

Drop the parentheses and every branch becomes its own boolean test. This is the form to reach for when the conditions have nothing to do with one another.

fun main() {
    val n = 84

    val size = when {
        n < 0 -> "negative"
        n < 10 -> "small"
        n < 100 -> "medium"
        else -> "large"
    }

    println(size)
}

//> medium

Branches are tested top to bottom, and the first match wins. 84 is not below 10, is below 100, so it stops there and never looks at the rest.

Exhaustiveness

Because a when can produce a value, the compiler insists that it always produces one. Used as an expression, a subject-less when always needs an else, and a when over an ordinary value needs one too. Used as a plain statement, else is optional — Kotlin only enforces coverage when something depends on the result.

Order is the whole trick

When several conditions can be true at once, the order of your branches is the logic.

Rendering diagram…

Put the narrowest test first. Move the divisible-by-3 test to the top and every multiple of 15 takes that branch instead — the most specific case never gets a chance to run.

Your exercise

FizzBuzz Single reads one integer and prints FizzBuzz, Fizz, Buzz, or the number itself. A subject-less when fits it in four branches.

The mistake the grader catches is branch order. The first visible test feeds 15, and if your divisible-by-3 branch sits above your divisible-by-15 branch you print Fizz and fail on the very first case. A hidden test feeds 7 and expects the bare number 7, so the final branch must print n itself — not the letter n, and not something like Number: 7.

Up nextLoopsControl Flow

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…