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.
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.
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.
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.
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.
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.
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) { ... }
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.
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
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.
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".
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