if and when

if, when and Kotlin's Expression-Oriented Style

Kotlin has no ternary operator because if returns a value: val fee = if (member) 0.0 else 4.99. when replaces switch, with no fall-through. With a subject it matches constants, ranges (in 1..5) or types (is String, smart-cast in the branch); without one, it tests Boolean conditions in order. A guard condition, stable since Kotlin 2.2, adds if to a branch of a when with a subject.

when with and without a subject, and a guard conditionKotlin
fun badge(book: Book): String = when {
  !book.inStock -> "sold out"
  book.rating >= 4.6 -> "top rated"
  book.price < 17.0 -> "bargain"
  else -> "-"
}
fun describe(x: Any): String = when (x) {
  is String if x.isBlank() -> "blank text"          // guard condition (Kotlin 2.2+)
  is String -> "text of length ${x.length}"
  is Int if x in 1..5 -> "a star rating"
  else -> "something else"
}
fun main() {
  println(catalog.map { "${it.title.substringAfterLast(' ')}: ${badge(it)}" })
  println(listOf("  ", "Salt", 4, 9).map(::describe))
}
Output
[Harbor: top rated, Web: -, Saffron: sold out, Summits: -, Paradox: bargain, Glass: -]
[blank text, text of length 4, a star rating, something else]

As an expression, every path must produce a value, and the compiler enforces it:

NoElse.kt: an if expression without an else branchKotlin
val inStock = true
val label = if (inStock) "Add to cart"
Output
NoElse.kt:2:13: error: 'if' must have both main and 'else' branches when used as an expression.
val label = if (inStock) "Add to cart"
            ^^

A when over an enum, a Boolean or a sealed type needs no else once every case is covered, which keeps the sealed UI states of UI State with Sealed Classes safe to extend.