skip to content

Show how if, when, and try-as-expression let you keep non-trivial logic in a single-expression function. What are the readability limits?

level: middleimportance: should knowfreq 50%

answer

  1. if/when/try are expressions → usable as the single expression
  2. when-as-expression must be exhaustive
  3. Sealed/enum subject → no else needed
  4. Compose with ?. and ?:
  5. Switch to block body when you need named locals/loops

basics

~20 s

In Kotlin, if, when, and try all produce values, so you can use one of them as the single expression after the equals sign. This lets a one-line function branch or handle errors while still returning the result of that branch.

solid answer

~40 s

Because `if`, `when`, and `try` are expressions in Kotlin (they evaluate to a value), they can each serve as the single expression of an expression-body function. A `when` expression used this way must be exhaustive (cover all cases / have an `else`) so it always yields a value. Example: `fun sign(n: Int) = when { n > 0 -> 1; n < 0 -> -1; else -> 0 }`. Likewise `fun parse(s: String) = try { s.toInt() } catch (e: NumberFormatException) { 0 }`. The readability limit: once the expression spans many branches, introduces side effects, or needs intermediate `val`s, a block body with named locals reads better. Expression bodies shine for pure mapping/branching; they obscure code that is really a sequence of steps. Detekt's style rules and team conventions often cap expression-body complexity.

code

kotlin · 6 lines
kotlin
fun classify(n: Int) = when {
    n % 15 == 0 -> "FizzBuzz"
    n % 3 == 0  -> "Fizz"
    n % 5 == 0  -> "Buzz"
    else        -> n.toString()
}

go deeper

for a junior

Knows if can be used like a ternary to return a value from a one-liner.

for a middle

Uses when/try as expressions and understands exhaustiveness requirements.

for a senior

Judges when an expression body harms readability and refactors to a block with named locals.

for a principal

Codifies complexity limits (detekt rules, review norms) so expression bodies stay scannable across the codebase.

## Expressions, not statements Kotlin has no ternary operator because `if` itself returns a value, and the same is true of `when` and `try`. Any of them can be the lone expression after `=`: ```kotlin // if as expression fun max(a: Int, b: Int) = if (a > b) a else b // when as expression — must be exhaustive to yield a value fun sign(n: Int) = when { n > 0 -> 1 n < 0 -> -1 else -> 0 } // try as expression fun parseOrZero(s: String) = try { s.toInt() } catch (e: NumberFormatException) { 0 } ``` ## Exhaustiveness When a `when` is **used as an expression** it must be exhaustive — every branch covered, or an `else`. For a sealed class or enum subject the compiler can verify exhaustiveness without `else`; otherwise you need one. A non-exhaustive `when` expression won't compile, because the function would have no value to return on the uncovered path. ```kotlin sealed interface Shape fun area(s: Shape) = when (s) { // exhaustive over sealed type, no else needed is Circle -> Math.PI * s.r * s.r is Square -> s.side * s.side } ``` ## Combining with elvis and safe calls These also compose into one expression: ```kotlin fun displayName(u: User?) = u?.name ?: "anonymous" ``` ## Readability limits Use a block body when: - you need several intermediate `val`s with meaningful names, - there are loops or repeated side effects (logging, I/O), - the single expression grows past a few branches and stops being scannable. Forcing a multi-step algorithm into one chained expression to keep the `=` form is an anti-pattern. Tools like **detekt** offer rules around function/expression complexity; teams often draw the line at "fits on a screen and reads top-to-bottom".

  • Why must a 'when' used as a single-expression body be exhaustive?
    Because the function must return a value on every path; a non-exhaustive when could fall through with no value, so the compiler rejects it unless all cases or an else are present.
  • Does using try as an expression change exception propagation?
    No. The catch still handles the exception; try-as-expression just yields the value of whichever block completes, so an uncaught exception still propagates normally.

saying these in an interview costs you the question

  • Thinking Kotlin needs a ternary operator (it uses if-expression instead)
  • Writing a non-exhaustive when as an expression and expecting it to compile
  • Cramming multi-step logic into one expression purely to keep the = form
  • Believing try cannot be used as an expression
  • Adding an unnecessary else over a sealed/enum subject already exhaustive

context