Everything Is an Expression
if, when, and try return values in Kotlin, so there's no ternary operator and no temporary-then-assign dance - and when is a far more powerful switch.
On this page
Here's a shift that quietly changes how you write code: in Kotlin, control-flow constructs are expressions -
if, when, and try return values. Java draws a hard line between statements (things that do something) and
expressions (things that have a value); Kotlin erases much of it. The practical upshots are no ternary operator
(you don't need one), no "declare-then-assign-in-each-branch" dance, and when - a switch so much better it
deserves its own introduction.
if is an expression
In Java, if is a statement, so to compute a value conditionally you either use the ternary ?: or declare a
variable and assign it in each branch. In Kotlin, if itself yields a value:
val max = if (a > b) a else b // if returns a value - no ternary needed
val label = if (score >= 90) { // branches can be blocks; the LAST expression is the value
"excellent"
} else if (score >= 60) {
"pass"
} else {
"fail"
}Because if/else produces a value, Kotlin has no ternary operator - it would be redundant. And you avoid the
common Java pattern of String label; if (...) label = ...; else label = ...; - just assign the if expression
directly to a val. (Note: for if to be an expression, an else is required - there must be a value in every
case.)
when: switch, evolved
when is Kotlin's replacement for switch, and it's dramatically more capable. As an expression, it returns a
value; and it matches far more than constants:
val description = when (x) {
0 -> "zero" // constant match
1, 2, 3 -> "small" // multiple values in one branch
in 4..10 -> "medium" // a RANGE
is String -> "it's a string" // a TYPE check (with smart cast)
else -> "large" // else is required when used as an expression
}when can match constants, multiple values, ranges (in 4..10), type checks (is String - and it smart-casts,
so inside that branch x is a String), and even arbitrary conditions when you omit the subject:
val grade = when { // no subject: each branch is a boolean condition
score >= 90 -> "A"
score >= 80 -> "B"
else -> "F"
}Compare Java's switch: no fall-through bugs (no break needed - branches don't fall through), no restriction to
constants, and it returns a value. It's closer to Java's newer switch expressions and pattern matching, but it
arrived earlier and is used everywhere in idiomatic Kotlin.
try is an expression too
Even try/catch returns a value, which is occasionally elegant:
val number = try {
input.toInt() // the value if it succeeds
} catch (e: NumberFormatException) {
0 // the value if it fails
}Why this matters: fewer temporaries, more val
The cumulative effect of expression-oriented control flow is that you assign the result of a decision directly
to an immutable val, instead of declaring a mutable var and mutating it across branches. That reinforces the
val-by-default habit from the last lesson: expressions let you compute-then-bind, so more of your variables can
be read-only. Less mutation, fewer temporaries, and code that reads as "this value is the result of this
decision."
Exhaustive when on sealed types is compiler-checked
when becomes especially powerful over a sealed type or enum: if you cover every case, you don't need an else, and if you later ADD a case, the compiler flags every when that no longer handles all of them. That's exhaustiveness checking - the same algebraic-data-type benefit from the advanced Java module, and a reason when + sealed classes (next module) is a favorite Kotlin pattern for modeling states safely.
Java statements are like a vending machine that, when you press a button, merely does something internally - you
then have to go check a separate slot to see what happened (declare a variable, inspect it after). Kotlin
expressions are a machine that hands you the item directly the moment you choose: assigning
val snack = when(choice) ... - the selection immediately becomes the thing you hold. You don't press a button and then rummage; the act
of deciding is the act of producing the value. when is the deluxe machine that also accepts ranges of coins,
groups of buttons, and 'anything sweet' as a selection - far more than the old model's one-button-one-item
switch - and it never accidentally dispenses two snacks because you forgot a break.
Convert this Java to idiomatic Kotlin using expressions and when: a method that takes an int httpStatus and returns a String category - 200-299 → 'success', 300-399 → 'redirect', 400-499 → 'client error', 500-599 → 'server error', anything else → 'unknown'. The Java version declares a String result, uses if/else-if, and returns it. Make the Kotlin as tight as idiomatic style allows.
What does it mean that if, when, and try are expressions in Kotlin?
Key takeaways
- In Kotlin, if, when, and try are expressions that return values, so you assign the result directly to a val.
- Because if returns a value, Kotlin has no ternary operator, and you avoid the declare-then-assign-per-branch pattern (an else is required for if used as an expression).
- when replaces switch and is far more capable: it matches constants, multiple values, ranges (in 4..10), and type checks (is String, with smart cast), and returns a value.
- when has no fall-through (no break) and can be subject-less, where each branch is a boolean condition - a cleaner if/else-if chain.
- Expression-oriented control flow reinforces val-by-default (compute then bind) and, over sealed types/enums, gives compiler-checked exhaustiveness.