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
- if/else for branching decisions
- How if is an expression that returns a value
- when as a clean replacement for switch
- Matching multiple values, ranges, and conditions in when
- Type-checking branches with is and smart casts
- Why some when expressions require an else
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)
- "'when' expression must be exhaustive" — Used as an expression, when must cover all cases. Add an else branch.
- Forgetting the arrow -> — Each branch is condition -> result. Missing the arrow is a syntax error.
- Using : like a switch — Kotlin uses ->, not a colon, and needs no break; only the matching branch runs.
- Assigning from an if with no else — val x = if (c) 1 won't compile. As an expression, if needs an else.
Pro Tips
- 💡 Ranges in when: in 1..10 matches any value in that inclusive range — great for grades and tiers.
- 💡 Argument-free when: drop the subject and use full conditions per branch — a clean if/else-if replacement.
- 💡 Return a when directly: a function body can be = when { ... } for a compact lookup function.
📋 Quick Reference — Control Flow
| Concept | Kotlin Syntax |
|---|---|
| if expression | val x = if (c) a else b |
| when on a value | when (n) { 1 -> ... } |
| Multiple values | 2, 3 -> "..." |
| Range branch | in 1..9 -> "..." |
| Type branch | is String -> ... |
| Catch-all | else -> "..." |
🎉 Lesson Complete!
- ✅ if is an expression — no ternary operator needed
- ✅ when replaces switch and is far more powerful
- ✅ Branches can match values, ranges (in), and types (is)
- ✅ As an expression, when needs an else
- ✅ Type branches give you free smart casts
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
- Previous: Operators & Strings
- Next: Control Flow (if, for, while) — if as an expression, for over ranges/collections, while, and labels
- Quick reference: Kotlin cheat sheet
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.