Expressions and control flow
An expression gives back a value. In Kotlin, if, when, try, a code block, and a function call can all give back a value. This often removes temporary variables and repeated assignments.
- Expression style
- Mutable style
val label = if (isOnline) "Connected" else "Offline"
var label: String
if (isOnline) label = "Connected" else label = "Offline"
The first version creates label once. There is no moment when it exists without a value, and it does not need to be mutable. Inside a block, the last expression becomes that branch's result.
val message = if (items.isEmpty()) {
logEmptyState()
"Nothing to display"
} else {
"${items.size} items"
}
Choosing if or when
Use if for binary or compound predicates. Use when when multiple meaningful cases exist.
fun httpMessage(code: Int): String = when (code) {
in 200..299 -> "Success"
401 -> "Sign in again"
404 -> "Not found"
in 500..599 -> "Server problem"
else -> "Unexpected response"
}
Exhaustive state handling
sealed interface LoadState {
data object Loading : LoadState
data class Content(val count: Int) : LoadState
data class Failed(val message: String) : LoadState
}
fun title(state: LoadState): String = when (state) {
LoadState.Loading -> "Loading"
is LoadState.Content -> "${state.count} results"
is LoadState.Failed -> state.message
}
For a sealed type, list every case instead of immediately adding else. If someone later adds a new state, the compiler points to each when that must be updated. That is useful protection during refactoring.
Loops and safe ranges
for (item in items) { /* value */ }
for (index in items.indices) { /* valid index */ }
for ((index, item) in items.withIndex()) { /* both */ }
for (value in 10 downTo 0 step 2) { /* 10, 8 ... 0 */ }
0..items.size includes items.size, which is invalid. Use items.indices or 0 until items.size.
Use while when repetition depends on a changing condition, such as fetching until a continuation token is absent.
In Kotlin, if and when are expressions, so they can return values. I use if for two-way conditions and when for several meaningful cases. With enums or sealed classes, an exhaustive when also lets the compiler tell me when a new case is not handled.