LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 517 min read

Control Flow

Master Kotlin's if and when as expressions, ranges, for and while loops, and labeled break/continue.

Introduction

Control flow determines which code runs, and how many times. Kotlin's control-flow constructs will look familiar if you know any C-family language, but with one major twist: `if` and `when` are expressions in Kotlin, meaning they can directly produce and return a value, not just branch execution.

This lesson covers `if` as an expression, the powerful `when` construct that replaces `switch`, ranges, and all of Kotlin's looping constructs.

if as an Expression

In Kotlin, `if` can be used the traditional way as a statement, or as an expression that evaluates to a value — eliminating the need for a separate ternary operator, which Kotlin deliberately does not have.

fun main() {
val age = 20
// Traditional statement form
if (age >= 18) {
println("Adult")
} else {
println("Minor")
}
// Expression form — if directly produces a value
val status = if (age >= 18) "Adult" else "Minor"
println(status)
}
Output

Click Run to see what this code prints.

when Instead of switch

`when` is Kotlin's replacement for Java's `switch` statement, and it is dramatically more powerful. It supports arbitrary expressions in each branch (not just constants), range checks, type checks, and — like `if` — can be used as an expression that returns a value.

fun describe(x: Any): String = when (x) {
1 -> "One"
2, 3 -> "Two or three"
in 4..10 -> "Between four and ten"
is String -> "A string of length ${x.length}"
else -> "Something else"
}
fun main() {
println(describe(1))
println(describe(7))
println(describe("Kotlin"))
println(describe(99.9))
}
Output

Click Run to see what this code prints.

when Without an Argument

You can also write `when` with no subject at all, in which case each branch is a boolean condition evaluated top to bottom — a clean alternative to a long if/else-if chain.

Ranges

Kotlin has a built-in range type, created with `..`, that represents a sequence of values. Ranges are used constantly with `when`, `for` loops, and membership checks with `in`.

fun main() {
val r = 1..5 // 1, 2, 3, 4, 5
println(3 in r) // true
println(9 in r) // false
val countdown = 5 downTo 1 // 5, 4, 3, 2, 1
val evens = 0..10 step 2 // 0, 2, 4, 6, 8, 10
val exclusive = 1 until 5 // 1, 2, 3, 4 (5 excluded)
println(evens.toList())
}
Output

Click Run to see what this code prints.

for Loops

Kotlin's `for` loop always iterates over something that provides an iterator — most commonly a range, a collection, or a string's characters. There is no traditional C-style `for (int i = 0; i < n; i++)` in Kotlin; ranges cover that use case instead.

fun main() {
for (i in 1..5) {
print("$i ")
}
println()
val languages = listOf("Kotlin", "Java", "Swift")
for (lang in languages) {
println("Language: $lang")
}
for ((index, lang) in languages.withIndex()) {
println("$index: $lang")
}
}
Output

Click Run to see what this code prints.

while and do-while Loops

`while` and `do-while` behave exactly as they do in Java and C: `while` checks its condition before the first iteration, and `do-while` guarantees the body runs at least once before checking.

fun main() {
var count = 0
while (count < 3) {
println("while: $count")
count++
}
var attempts = 0
do {
println("do-while: $attempts")
attempts++
} while (attempts < 3)
}
Output

Click Run to see what this code prints.

Labeled break and continue

By default, `break` and `continue` only affect the nearest enclosing loop. Kotlin lets you label a loop and target it explicitly, which is especially useful for breaking out of nested loops in one step.

fun main() {
outer@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) continue@outer
println("i=$i, j=$j")
}
}
}
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Looking for a ternary operator (`? :`) — Kotlin deliberately has none; use `if` as an expression instead.
  • Writing a C-style indexed `for (int i = 0; i < n; i++)` — Kotlin uses ranges (`0 until n`) for that pattern.
  • Forgetting `when` branches are checked top to bottom and stop at the first match — order matters, especially with range checks.
  • Missing an `else` branch on a `when` used as an expression — the compiler requires it to guarantee exhaustiveness unless the subject type is provably covered.

Best Practices

  • Prefer `when` over long `if`/`else if` chains once you have more than two or three branches — it reads far more clearly.
  • Use `if`/`when` as expressions directly assigned to a `val` instead of declaring a `var` and mutating it inside each branch.
  • Use `until` when you want to exclude the upper bound (common for indices) and `..` when you want to include it.
  • Reach for labeled break/continue sparingly — if nested loops are getting hard to follow, consider extracting a function instead.

Frequently Asked Questions

No, and it doesn't need one — since `if` is already an expression, `if (cond) a else b` serves exactly the same purpose.

Yes, using `is`, as shown in the describe() example above. Combined with smart casts, the matched branch can use the variable as that specific type without an explicit cast.

Kotlin's designers considered the C-style for loop error-prone (off-by-one bugs, forgetting to increment). Ranges combined with the for-in loop cover the same use cases more safely and readably.

Key Takeaways

  • `if` is an expression in Kotlin and can be assigned directly to a value — there is no separate ternary operator.
  • `when` replaces `switch`, supports ranges, types, and multiple values per branch, and can also be used as an expression.
  • Ranges (`..`, `until`, `downTo`, `step`) power most iteration and membership checks.
  • `for` iterates over ranges, collections, and anything else that provides an iterator — there is no classic indexed for loop.
  • Labeled break/continue let you target an outer loop explicitly from inside nested loops.

Summary

You can now branch and loop through Kotlin code idiomatically, using if and when as expressions and ranges for iteration. Next, you'll learn how to organize logic into reusable functions.

Next Lesson →

Functions