when Workbook

Ten short exercises on Kotlin's when — as a statement and an expression, with and without a subject, range and type matching, multiple values per branch, and exhaustiveness.

Practice problems for Kotlin’s when Is the New switch. Each takes a minute or two. Most exercises auto-check: implement the function in the editor and press Run — hidden tests go green when you’re right and red (with a hint) when you’re not, all on JetBrains’ Kotlin server. A couple that print or read input stay attempt-then-reveal: press Run to try them, then click Show answer. Nothing here is a trick question, just direct practice of the syntax from the lesson.

with a subject

1. Day classification

Implement dayKind so it returns "weekday" for days 1–5, "weekend" for 6–7, and "invalid" otherwise — using when with a subject and ranges.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun dayKind() { Assert.assertEquals("1..5 are weekdays", "weekday", dayKind(3)) Assert.assertEquals("6..7 are the weekend", "weekend", dayKind(6)) Assert.assertEquals("anything else is invalid", "invalid", dayKind(9)) } } //sampleStart fun dayKind(day: Int): String = "invalid" // TODO: "weekday" for 1..5, "weekend" for 6..7 //sampleEnd
Show answer Hide answer
fun dayKind(day: Int) = when (day) {
    in 1..5 -> "weekday"
    in 6..7 -> "weekend"
    else -> "invalid"
}

2. Multiple values per branch

Implement isVowel so it returns true when the Char is a lowercase vowel, false otherwise — one branch listing several values.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun isVowel() { Assert.assertTrue("'e' is a vowel", isVowel('e')) Assert.assertFalse("'z' is not a vowel", isVowel('z')) } } //sampleStart fun isVowel(c: Char): Boolean = false // TODO: true for 'a', 'e', 'i', 'o', 'u' //sampleEnd
Show answer Hide answer
fun isVowel(c: Char) = when (c) {
    'a', 'e', 'i', 'o', 'u' -> true
    else -> false
}

3. Assign from a when

Implement sizeFor so it returns "small" (count < 10), "medium" (< 100), or "large" (otherwise) — using a subjectless when.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun sizeFor() { Assert.assertEquals("under 10 is small", "small", sizeFor(3)) Assert.assertEquals("under 100 is medium", "medium", sizeFor(42)) Assert.assertEquals("otherwise large", "large", sizeFor(500)) } } //sampleStart fun sizeFor(count: Int): String = "large" // TODO: "small" (< 10), "medium" (< 100), else "large" //sampleEnd
Show answer Hide answer
fun sizeFor(count: Int) = when {
    count < 10 -> "small"
    count < 100 -> "medium"
    else -> "large"
}

type matching

4. Describe an Any

Implement describe so it returns "int" for an Int, "text" for a String, and "other" for anything else — matching on type.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun describe() { Assert.assertEquals("an Int is int", "int", describe(7)) Assert.assertEquals("a String is text", "text", describe("hi")) Assert.assertEquals("anything else is other", "other", describe(3.0)) } } //sampleStart fun describe(x: Any): String = "other" // TODO: "int" for Int, "text" for String //sampleEnd
Show answer Hide answer
fun describe(x: Any) = when (x) {
    is Int -> "int"
    is String -> "text"
    else -> "other"
}

5. Use the smart-cast value

Implement lengthOrZero so it returns the string’s length when x is a String, otherwise 0 — using the smart cast inside the branch.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun lengthOrZero() { Assert.assertEquals("a String gives its length", 5, lengthOrZero("hello")) Assert.assertEquals("anything else gives 0", 0, lengthOrZero(42)) } } //sampleStart fun lengthOrZero(x: Any): Int = 0 // TODO: x.length when x is String //sampleEnd
Show answer Hide answer
fun lengthOrZero(x: Any) = when (x) {
    is String -> x.length
    else -> 0
}

ranges and capture

6. Range branches

Implement bucket so it returns "digit" for 0–9, "teens" for 10–19, and "big" otherwise — with range branches.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun bucket() { Assert.assertEquals("0..9 is a digit", "digit", bucket(4)) Assert.assertEquals("10..19 is teens", "teens", bucket(15)) Assert.assertEquals("anything larger is big", "big", bucket(42)) } } //sampleStart fun bucket(n: Int): String = "big" // TODO: "digit" for 0..9, "teens" for 10..19 //sampleEnd
Show answer Hide answer
fun bucket(n: Int) = when (n) {
    in 0..9 -> "digit"
    in 10..19 -> "teens"
    else -> "big"
}

7. Capture the subject

Rewrite this so the result of readLine() is captured in the when header and reused:

val line = readLine()
when (line) { ... }
Try it — edit, then press Run fun main() { val line = readLine() // TODO: capture the value in the when header: when (val line = readLine()) { ... } when (line) { null -> println("no input") else -> println(line.length) } }
Show answer Hide answer
when (val line = readLine()) {
    null -> println("no input")
    else -> println(line.length)
}

exhaustiveness

8. No else for enums

Given enum class Direction { NORTH, SOUTH, EAST, WEST }, implement opposite so it maps each direction to its opposite — a when expression with no else branch.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert enum class Direction { NORTH, SOUTH, EAST, WEST } class Test { @Test fun opposite() { Assert.assertEquals("north's opposite is south", Direction.SOUTH, opposite(Direction.NORTH)) Assert.assertEquals("east's opposite is west", Direction.WEST, opposite(Direction.EAST)) } } //sampleStart fun opposite(d: Direction): Direction = d // TODO: map each direction to its opposite (exhaustive, no else) //sampleEnd
Show answer Hide answer
fun opposite(d: Direction) = when (d) {
    Direction.NORTH -> Direction.SOUTH
    Direction.SOUTH -> Direction.NORTH
    Direction.EAST -> Direction.WEST
    Direction.WEST -> Direction.EAST
}

Covering every constant makes the when exhaustive, so no else is needed.

9. Replace instanceof + cast

Implement measure to replace this Java-style chain with an idiomatic when:

if (shape is Circle) return shape.radius
if (shape is Square) return shape.side
Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Circle(val radius: Double) class Square(val side: Double) class Test { @Test fun measure() { Assert.assertEquals("a Circle gives its radius", 2.0, measure(Circle(2.0)), 0.0001) Assert.assertEquals("a Square gives its side", 3.0, measure(Square(3.0)), 0.0001) Assert.assertEquals("anything else gives 0.0", 0.0, measure("x"), 0.0001) } } //sampleStart fun measure(shape: Any): Double = 0.0 // TODO: shape.radius for Circle, shape.side for Square //sampleEnd
Show answer Hide answer
fun measure(shape: Any) = when (shape) {
    is Circle -> shape.radius
    is Square -> shape.side
    else -> 0.0
}

10. Statement, not expression

Write a when that just prints a message for each level (1, 2, else) — used as a statement, returning nothing.

Try it — edit, then press Run fun main() { val level = 2 when (level) { else -> println("unknown") // TODO: "low" for 1, "high" for 2, else "unknown" } }
Show answer Hide answer
when (level) {
    1 -> println("low")
    2 -> println("high")
    else -> println("unknown")
}

Going deeper: guards and exhaustiveness

12. A branch with a guard

Kotlin lets a when branch test a type and a predicate with an if guard. Complete classify so a circle with radius over 10 is "big circle", any other circle is "small circle", a square (equal sides) is "square", and any other rectangle is "rectangle".

Implement it, then press Run to check import org.junit.Test import org.junit.Assert sealed interface Shape { data class Circle(val r: Double) : Shape data class Rect(val w: Double, val h: Double) : Shape } class Test { @Test fun classify() { Assert.assertEquals("big circle", classify(Shape.Circle(12.0))) Assert.assertEquals("small circle", classify(Shape.Circle(3.0))) Assert.assertEquals("square", classify(Shape.Rect(4.0, 4.0))) Assert.assertEquals("rectangle", classify(Shape.Rect(4.0, 8.0))) } } //sampleStart fun classify(s: Shape): String = when (s) { else -> "" // TODO: use guard conditions (is X if cond ->) } //sampleEnd
Show answer Hide answer
fun classify(s: Shape): String = when (s) {
    is Shape.Circle if s.r > 10 -> "big circle"
    is Shape.Circle -> "small circle"
    is Shape.Rect if s.w == s.h -> "square"
    is Shape.Rect -> "rectangle"
}

The smart cast is already in effect inside the guard (s.r, s.w), and because each type has an unguarded catch-all branch, the when stays exhaustive with no else.

13. Why no else?

The classify above is a when expression over a sealed type with no else, and it compiles. What happens if someone adds a third Shape variant later? Reveal to check.

Show answer Hide answer

Every exhaustive when over Shape becomes a compile error pointing at exactly the code that no longer covers all cases. That’s the whole reason to omit the else on a sealed or enum when — a defensive else would silently swallow the new variant instead of flagging it.


Back to the lesson, Kotlin’s when Is the New switch, or on to the next one: smart casts.

Comments