Kotlin Control Flow Explained
Kotlin's control flow isn't just flow control — if and when are expressions with return values, making Charlie's order routing logic both safe and concise.
1. What You'll Learn
ifis an expression:val max = if (a > b) a else bwhenin all its forms: multi-value, guard conditions, variable bindingfor+ iterator: iterate over collections, ranges, maps- Labels and returns:
break@label/continue@label/return@forEach - Charlie in action:
when(order.status)order routing
2. An Architect's Real Story
(1) Pain Point: Nested if-else Hell
Charlie's Java OrderProcessor used 5 levels of nested if-else to route orders by status — 120 lines just to decide which pipeline to use. When new team member Bob joined, he got the branch conditions wrong 3 times, causing production incidents.
(2) Kotlin's when Expression Solution
KOTLIN
// Clean, exhaustive, compiler-verified
fun route(order: Order): Pipeline = when (order.status) {
Status.PENDING -> PendingPipeline
Status.CONFIRMED -> if (order.total > 10_000) VipPipeline else StandardPipeline
Status.SHIPPED -> TrackingPipeline
Status.CANCELLED -> RefundPipeline
}
whenexpression exhaustiveness checking + compiler guard: a missing branch is a compile error. Bob can never introduce such bugs again.
3. if Expression
(1) if is an Expression with a Return Value
KOTLIN
// if as expression
val max = if (a > b) a else b
// Multi-branch if expression
val category = if (total > 10_000) {
println("High value order")
"VIP"
} else if (total > 1_000) {
"PRIORITY"
} else {
"STANDARD"
}
(2) if Expression vs Java if
| Dimension | Java if | Kotlin if |
|---|---|---|
| Nature | Statement | Expression |
| Return value | None | Value of last expression |
| Assignment | Needs extra variable | val x = if(...)... |
| Ternary operator | ?: |
if/else |
KOTLIN
// No ternary operator needed - if/else IS the ternary
val discount = if (total > 5_000) 0.15 else if (total > 1_000) 0.10 else 0.0
4. when in All Its Forms
(1) Basic when Expression
KOTLIN
// Exact match
val label = when (status) {
"PENDING" -> "Waiting"
"CONFIRMED" -> "Confirmed"
else -> "Unknown"
}
// Multiple values in one branch
val isTerminal = when (status) {
"SHIPPED", "DELIVERED", "CANCELLED" -> true
else -> false
}
(2) Guard Conditions (when with Guard)
KOTLIN
// Conditional branches with 'in' and ranges
val pipeline = when {
status == "CANCELLED" -> "REFUND"
total > 10_000 && priority == "URGENT" -> "VIP_EXPRESS"
total > 10_000 -> "VIP"
total > 1_000 -> "PRIORITY"
status in listOf("NEW", "PENDING") -> "QUEUE"
else -> "STANDARD"
}
(3) Variable Binding and Destructuring
KOTLIN
// Capture with when subject
val result = when (val response = httpClient.call()) {
is Success -> "OK: ${response.data}"
is Error -> "Failed: ${response.code}"
}
// response is smart-cast and available in branches
(4) Order Status Routing Diagram
flowchart TD
A[when order.status] --> B{PENDING}
A --> C{CONFIRMED}
A --> D{SHIPPED}
A --> E{CANCELLED}
B --> B1[Waiting Queue]
C --> C1{total > 10000?}
C1 -->|Yes| C2[VIP Pipeline]
C1 -->|No| C3[Standard Pipeline]
D --> D1[Tracking Pipeline]
E --> E1[Refund Queue]
(5) when Forms Comparison
| Form | Syntax | Use Case |
|---|---|---|
| Exact match | when(status) |
Enums / strings / constants |
| Multi-value match | "A", "B" -> ... |
Multiple values, same branch |
| Range match | in 1..10 |
Numeric intervals |
| Type match | is String |
Type-based routing |
| Guard conditions | when { ... } |
Complex condition combos |
| Variable binding | when(val x = ...) |
Capture computed values |
5. for Loop and Iterators
(1) Basic for Loop
KOTLIN
// Range iteration
for (i in 1..10) { /* i from 1 to 10 */ }
// List iteration
for (order in orders) { println(order.id) }
// With index
for ((index, order) in orders.withIndex()) {
println("[$index] ${order.id}")
}
// Map iteration
for ((id, status) in orderStatusMap) {
println("$id: $status")
}
(2) Iterator Protocol
KOTLIN
// Any class with operator fun iterator() can be used in for-loop
class OrderBatch(val orders: List<Order>) {
operator fun iterator(): Iterator<Order> = orders.iterator()
}
val batch = OrderBatch(orders)
for (order in batch) { /* works! */ }
(3) for Loop Patterns Comparison
| Pattern | Syntax | Use Case |
|---|---|---|
| Range | for (i in 1..10) |
Counting loops |
| Collection | for (o in orders) |
Iterating elements |
| With index | for ((i, o) in ...) |
Need index |
| Map | for ((k,v) in map) |
Key-value iteration |
| Custom iterator | operator fun iterator() |
Custom containers |
6. while Loop
KOTLIN
// Standard while
var retryCount = 0
while (retryCount < 3) {
val success = tryProcessOrder()
if (success) break
retryCount++
}
// do-while (executes at least once)
var input: String
do {
input = readOrderStatus()
} while (input !in listOf("CONFIRM", "CANCEL"))
7. Labels and Returns
(1) Loop Labels
KOTLIN
// Label outer loop
loop@ for (batch in batches) {
for (order in batch) {
if (order.status == "CANCELLED") continue@loop // Skip to next batch
if (order.total > 100_000) break@loop // Stop all processing
process(order)
}
}
(2) Lambda Returns
KOTLIN
// return from lambda (local return)
orders.forEach { order ->
if (order.status == "SKIP") return@forEach // Skip this item only
process(order)
}
// return from enclosing function (non-local return)
// Only works in inline functions
orders.forEach { order ->
if (order.status == "FATAL") return // Exits main()
process(order)
}
(3) Label Types Comparison
| Label Type | Syntax | Effect |
|---|---|---|
| Loop label | loop@ + break@loop |
Exit specified loop |
| continue label | continue@loop |
Jump to next iteration of specified loop |
| Lambda local return | return@forEach |
Exit only current lambda |
| Non-local return | return |
Exit enclosing function (inline only) |
8. Complete Example: OrderProcessor Order State Machine
KOTLIN
// ============================================
// OrderProcessor - Status-Based Routing
// Feature: Full order status routing with when
// ============================================
enum class Status { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }
data class Order(val id: String, val total: Double, val status: Status, val region: String)
fun routeOrder(order: Order): String = when (order.status) {
Status.PENDING -> when {
order.region == "EU" -> "EU_COMPLIANCE_QUEUE"
order.total > 10_000 -> "HIGH_VALUE_QUEUE"
else -> "STANDARD_QUEUE"
}
Status.CONFIRMED -> when {
order.total > 5_000 -> "EXPRESS_SHIPPING"
else -> "REGULAR_SHIPPING"
}
Status.SHIPPED -> "TRACKING_UPDATE"
Status.DELIVERED -> "COMPLETION_QUEUE"
Status.CANCELLED -> when {
order.total > 1_000 -> "PRIORITY_REFUND"
else -> "STANDARD_REFUND"
}
}
fun processBatch(orders: List<Order>) {
loop@ for (order in orders) {
when (order.status) {
Status.CANCELLED -> {
println("SKIP: ${order.id} cancelled")
continue@loop
}
else -> {
val pipeline = routeOrder(order)
val priority = if (order.total > 10_000) "CRITICAL" else "NORMAL"
println("PROCESS: ${order.id} -> $pipeline [$priority]")
}
}
}
}
fun main() {
val orders = listOf(
Order("ORD-001", 299.99, Status.CONFIRMED, "US"),
Order("ORD-002", 15_000.00, Status.PENDING, "EU"),
Order("ORD-003", 2_500.00, Status.CONFIRMED, "US"),
Order("ORD-004", 45.50, Status.CANCELLED, "US"),
Order("ORD-005", 800.00, Status.PENDING, "ASIA"),
Order("ORD-006", 8_000.00, Status.SHIPPED, "US")
)
println("=== Order Routing ===")
processBatch(orders)
// Summary using when expression results
val summary = orders
.filter { it.status != Status.CANCELLED }
.groupBy { routeOrder(it) }
.mapValues { (pipeline, list) -> "${list.size} orders" }
println("\n=== Pipeline Summary ===")
summary.forEach { (pipeline, info) -> println(" $pipeline: $info") }
}
Output:
TEXT
=== Order Routing ===
PROCESS: ORD-001 -> REGULAR_SHIPPING [NORMAL]
PROCESS: ORD-002 -> EU_COMPLIANCE_QUEUE [CRITICAL]
PROCESS: ORD-003 -> EXPRESS_SHIPPING [NORMAL]
SKIP: ORD-004 cancelled
PROCESS: ORD-005 -> STANDARD_QUEUE [NORMAL]
PROCESS: ORD-006 -> TRACKING_UPDATE [NORMAL]
=== Pipeline Summary ===
REGULAR_SHIPPING: 1 orders
EU_COMPLIANCE_QUEUE: 1 orders
EXPRESS_SHIPPING: 1 orders
STANDARD_QUEUE: 1 orders
TRACKING_UPDATE: 1 orders
❓ FAQ
Q Does Kotlin have a ternary operator
?:?A No. Kotlin uses
if/else instead of a ternary operator because if is an expression — functionally equivalent and more readable.Q What's the difference between
when with and without a subject?A
when(x) matches against x's value/type with compiler exhaustiveness checking; when { } is arbitrary conditional expressions and must include else.Q Can a for loop iterate over custom objects?
A Yes, as long as the class implements
operator fun iterator() returning an Iterator.Q What are the limitations of non-local return?
A Non-local return can only be used in lambdas of
inline functions. Using non-local return in a regular lambda causes a compile error.Q Does branch order matter in
when?A Yes.
when matches top-to-bottom, and the first satisfied branch wins. Put more specific conditions before more general ones.Q How do I match both a value and a condition in
when?A Use guard conditions:
is Order && total > 1000 -> — first match the type, then add the condition.📖 Summary
ifis an expression that can be directly assigned to variables, replacing Java's ternary operatorwhenisswitch's upgraded version: multi-value matching, ranges, types, guard conditions- When
whenis used as an expression, the compiler enforces exhaustiveness to prevent missing branches forsupports ranges, collections, maps, and custom iterators- Labels with
break@+continue@precisely control nested loop navigation - Lambda local return
return@forEachvs non-local returnreturn
📝 Exercises
- Beginner (⭐): Use
ifexpressions to implement amaxOfThreefunction returning the maximum of three numbers. Hint: nested if or usemaxOf()from the standard library - Intermediate (⭐⭐): Use
whento implement order priority determination: VIP (>10000 USD) > High Priority (>1000) > Normal, skip cancelled orders. Hint:whenguard conditions - Challenge (⭐⭐⭐): Use loop labels to implement batch order processing: outer loop over customer batches, inner loop over orders — skip the entire customer batch if a cancelled order is found. Hint:
loop@+continue@loop



