Start Learning
Javaneer
Back to stage
Stage 1·Syntax for Java Eyes

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.

14 min readIntermediate
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.

A vending machine that hands you the item, vs. one that just beeps

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.

Rewrite Java statements as Kotlin expressions

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.
Was this lesson helpful?
Edit this page on GitHub