Control Flow
|
This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Kotlin’s control-flow constructs will look familiar, with one structural difference from Java that runs through
the whole language: if and when are expressions, not only statements.
if as an Expression
if/else can produce a value directly, replacing Java’s ternary operator (which Kotlin does not have) and,
for anything more than a one-liner, replacing an assignment split across several statements:
val a = 5
val b = 2
val max = if (a > b) a else b // an expression -- no ternary operator needed
val description = if (max > 10) {
"big"
} else {
"small" // the last expression in each branch is the branch's value
}
When used as an expression, if must have an else branch (the compiler needs a value on every path); used
as a plain statement whose value is discarded, else remains optional exactly as in Java.
when as an Expression
when replaces Java’s switch and is far more general: its branches can match exact values, ranges,
type checks, or arbitrary boolean conditions, and — like if — it can produce a value:
val x = 7
val category = when { // no subject: each branch is its own boolean condition
x < 0 -> "negative"
x == 0 -> "zero"
x % 2 == 0 -> "positive even"
else -> "positive odd"
}
fun describe(value: Any): String = when (value) {
1, 2, 3 -> "small number" // comma-separated values share a branch
in 4..10 -> "medium number" // range membership
is String -> "a string of length ${value.length}" // type check + smart cast, see below
else -> "something else"
}
When when has a subject (when (value)) over a sealed type or an enum, the compiler can enforce
exhaustiveness at compile time with no else needed — covered in depth in
Sealed Classes and Enums. Inside an is branch, value
is automatically smart-cast to that type for the rest of the branch — no explicit cast needed (see
Null Safety for the general smart-cast rule).
for and while
for iterates any Iterable (a collection, a range/progression, an array) — Kotlin has no C-style
three-clause for. while/do-while test before/after each iteration exactly as in Java:
for (i in 1..5) print(i) // over a range -- see Operators and Ranges
for (c in "abc") print(c) // Strings are Iterable<Char>
for ((index, value) in listOf("a", "b").withIndex()) {
println("$index: $value") // destructuring the IndexedValue -- see Destructuring
}
var n = 5
while (n > 0) {
print(n--) // 54321
}
break, continue, and Labels
break/continue behave as in Java for the innermost loop; a label@ prefix lets either target an outer loop
explicitly, which is also how a lambda’s implicit loop-like control flow (forEach, for example) can be exited
early:
outer@ for (row in 0..2) {
for (col in 0..2) {
if (row + col == 3) break@outer // leaves BOTH loops
if (col == row) continue@outer // next row
println("$row,$col")
}
}
listOf(1, 2, 3, 4).forEach lit@{
if (it == 3) return@lit // "continue" for this one lambda invocation only
println(it)
}
return@label inside a lambda is Kotlin’s way of returning from that lambda rather than the enclosing
function — necessary because a bare return inside an inline lambda (like `forEach’s) returns from the
enclosing function itself, which is almost never what’s wanted; see
Lambdas and Higher-Order Functions for the
full non-local-return rule.
See Also
-
Sealed Classes and Enums — exhaustive
whenover a closed type hierarchy. -
Null Safety — smart casts in
if/whenconditions. -
Lambdas and Higher-Order Functions — labeled returns from a lambda.