skip to content

When is the else branch mandatory for an if in Kotlin, and when is it optional? Show an example of each.

level: middleimportance: must knowfreq 60%

answer

  1. Result used = expression = else required
  2. Result discarded = statement = else optional
  3. Block branch value = last expression
  4. return/throw branch has type Nothing
  5. Mandatory else makes if a total ternary

basics

~20 s

If you use the if's result (assign it, return it, pass it), you must include else so every path produces a value. If you ignore the result and just run side effects, else is optional.

solid answer

~50 s

The rule hinges on whether `if` is used as an **expression** or a **statement**. As an expression — when its result is assigned, returned, or passed as an argument — every execution path must yield a value, so `else` is **mandatory**. `val x = if (c) 1` does not compile because the false path would have no value. As a statement — when you discard the result and only run side effects — `else` is **optional**: `if (c) doThing()` is fine. The compiler decides which mode applies from context. A subtle case: in a function whose body is a single `if`, if the branches `return`/`throw` (type `Nothing`) the compiler can still verify all paths terminate, but if you assign the whole `if` to a variable you still need `else`. The mandatory-else rule is what makes `if` a safe ternary replacement: there is always a defined value.

code

kotlin · 10 lines
kotlin
fun statementMode(c: Boolean) {
    if (c) println("hi")          // OK, no else
}

fun expressionMode(c: Boolean): Int {
    val v = if (c) 1 else 0       // else required
    return v
}

// fun broken(c: Boolean): Int = if (c) 1  // does NOT compile

go deeper

for a junior

States that else is needed when assigning the if's result and optional for plain side-effect ifs.

for a middle

Articulates the expression-vs-statement distinction and shows both cases with correct examples.

for a senior

Explains Nothing-typed throw/return branches and how block branches resolve to their last expression.

for a principal

Connects mandatory-else totality to type safety and the ternary-replacement design rationale.

## Expression mode vs statement mode Kotlin's `if` works in two modes, and the `else` requirement depends on the mode. ### Statement mode — else optional When the result is **not used**, `if` is a plain control-flow statement. You may omit `else`: ```kotlin if (user.isAdmin) grantAccess() // no else needed ``` Nothing consumes a value, so a missing branch simply does nothing. ### Expression mode — else mandatory When the result **is used** — assigned to a `val`/`var`, returned, or passed as an argument — `if` is an expression and **must** have `else`: ```kotlin val grade = if (score >= 60) "pass" else "fail" // else required ``` Without `else`, the false case would produce no value, so the type would be undefined. The compiler rejects: ```kotlin // val grade = if (score >= 60) "pass" // ERROR: 'if' must have both branches ``` ### Why this exists This is precisely what lets `if` replace the ternary safely: an assigned `if` is **total** — it always yields a value. ### Edge case: Nothing-typed branches A branch that `return`s or `throw`s has type **Nothing** (the type with no values, a subtype of every type). The compiler treats such a branch as non-returning. So this single-expression function is valid: ```kotlin fun describe(n: Int): String = if (n > 0) "positive" else throw IllegalArgumentException("non-positive") ``` Here `else` is still present; the `throw` branch contributes `Nothing`, and the overall type narrows to `String`. ### Block branches Branches can be blocks; the **last expression** of a block is its value: ```kotlin val n = if (flag) { log("taking true path") 42 // value of the block } else { 0 } ``` ### Summary table - Result used (expression) → `else` required. - Result discarded (statement) → `else` optional.

  • Why does a branch that throws still let the expression compile with a single resolved type?
    throw produces type Nothing, a subtype of every type. The expression type becomes the other branch's type, since Nothing imposes no upper bound.
  • What is the value of a block branch?
    The value of its last expression. Earlier lines run as side effects; the final expression determines the branch's result.

saying these in an interview costs you the question

  • Saying else is always required for every if
  • Saying else is never required
  • Not connecting the rule to expression vs statement usage
  • Believing a throw branch needs a fake return value
  • Thinking a block branch returns its first line

context