How does try/catch behave as an expression in Kotlin? What value does it produce, and how does finally interact with that value?
answer
- try yields last expr of try or matching catch
- finally does not supply the value
- Branches need a common supertype
- throw-only catch has type Nothing
- Use it to initialize a val
basics
~20 sIn Kotlin try/catch can return a value. The result is the last expression of whichever block runs — the try block if it succeeds, or the matching catch block if it fails. A finally block runs but does not change the returned value.
solid answer
~50 s`try` is an expression: `val x = try { parse(s) } catch (e: NumberFormatException) { 0 }`. The value is the last expression of the `try` block if no exception is thrown, or the last expression of the matching `catch` block if one is. A `finally` block always runs (cleanup) but its body does **not** contribute to the expression's value — only `try`/`catch` blocks do. For the expression to type-check, the `try` and all relevant `catch` branches must yield a common type (their least common supertype); if a branch only throws or never completes, its type is `Nothing`, which fits any expected type. This lets you initialize a `val` based on whether an operation succeeded, avoiding a mutable `var` written in two places. If a `catch` block itself rethrows or returns, control leaves before producing a value.
code
kotlin · 8 linesfun parsePort(raw: String): Int =
try {
raw.trim().toInt()
} catch (e: NumberFormatException) {
8080 // fallback supplies the value
} finally {
log.debug("parsePort done") // ignored for the value
}go deeper
Recognizes that try can return a value assigned to a val.
Explains try vs catch supplies the value and that finally does not.
Reasons about the common-supertype typing and the Nothing role in throw-only branches.
Weighs readability vs. the return-in-finally anti-pattern and codifies team guidance on expression-style try usage.
## try/catch is an expression In Kotlin, unlike Java, `try` is an **expression**: it produces a value you can assign or return directly. ```kotlin val count: Int = try { line.trim().toInt() // value if no throw } catch (e: NumberFormatException) { 0 // value if NumberFormatException } ``` ### Which block supplies the value - If the `try` block completes normally, the expression's value is the **last expression** of the `try` block. - If a matching `catch` runs, the value is the **last expression** of that `catch` block. - The `finally` block, if present, **does not** contribute to the value. It is for cleanup only. ```kotlin val x = try { 1 } finally { 2 } // x == 1, not 2 ``` ## Typing rules For the whole `try` expression to have a type, the contributing blocks (`try` and each `catch`) must have a **common supertype**. The compiler picks the least common supertype. A block that always `throw`s, `return`s, or loops forever has type **`Nothing`**, the bottom type that is a subtype of everything. So this type-checks even though the catch never yields an Int: ```kotlin val n: Int = try { s.toInt() } catch (e: Exception) { throw IllegalStateException("bad: $s") } ``` Here the `catch` has type `Nothing`, which is a subtype of `Int`, so the overall type is `Int`. ## Why this matters It lets you assign an immutable `val` cleanly instead of declaring a `var` and writing it from two places: ```kotlin // idiomatic Kotlin val config = try { load() } catch (e: IOException) { Defaults } ``` ## finally semantics - `finally` always runs: on normal completion, on a caught exception, and even on an uncaught one propagating out. - A `return` inside `finally` will **override** any value or exception from `try`/`catch` (an anti-pattern that swallows exceptions) — but a plain expression as the last line of `finally` is ignored. ## Key terms - **Expression**: code that yields a value (vs. a statement). - **`Nothing`**: the type with no instances; subtype of every type; the type of a block that never returns normally. - **Least common supertype**: the narrowest type all branches share.
- Given val x = try { 1 } finally { 2 }, what is x?1 — the finally block runs but never contributes to the expression's value.
- Why does 'try { s.toInt() } catch (e: Exception) { throw ... }' still type as Int?The catch block's last action is a throw, giving it type Nothing, which is a subtype of Int, so the common type is Int.
saying these in an interview costs you the question
- Saying the finally block determines the returned value
- Thinking try/catch is only a statement like in Java
- Not knowing all branches must share a common type
- Unaware that a throw-only catch has type Nothing