skip to content

When a Kotlin `if/else` (or `when`) expression has one branch returning String and another returning null, what is the inferred type of the whole expression, and what general rule produces it?

level: middleimportance: must knowfreq 65%

answer

  1. Branch type = least upper bound (LUB)
  2. null has type Nothing?
  3. LUB(String, Nothing?) = String?
  4. Any branch nullable => whole result nullable
  5. Unrelated branches climb to Any / Any?

basics

~10 s

The expression's type is String?. Kotlin takes the common supertype of all branches. One branch is String, the other is null (type Nothing?), and the smallest type covering both is String?.

solid answer

~40 s

Kotlin infers the type of an `if/else` or `when` *expression* as the **least upper bound (LUB)** — the most specific common supertype — of its branch types. With branches `String` and `null`, the literal `null` has type `Nothing?`. Since `Nothing` is the bottom type and `Nothing?` is below every nullable type, `LUB(String, Nothing?)` is `String?`. The nullability is unified upward: if any branch can produce null, the result type becomes nullable. The same logic gives `LUB(Int, Int?) = Int?` and `LUB(String, Int) = Any` (or `Comparable<*> & Serializable`-style intersection in practice, but conceptually a common supertype). This is why `val x = if (c) "a" else null` yields `String?` rather than an error or `Any?`.

code

kotlin · 4 lines
kotlin
val a = if (cond) "hi" else null   // String?
val b = if (cond) 1 else null      // Int?
val c = when (k) { 1 -> "one"; else -> null }  // String?
val d = if (cond) "s" else 1       // Any (or intersection of common supertypes)

go deeper

for a junior

Recognizes the result is String? and that a null branch makes the whole thing nullable.

for a middle

Names the least-upper-bound rule, knows null is Nothing?, and computes LUB(String, Nothing?) = String?.

for a senior

Explains LUB for unrelated branches climbing to Any/intersection types and how nullability unifies upward through the lattice.

for a principal

Discusses how the compiler computes intersection-style common supertypes and the implications for inference stability and API design.

## if/else is an expression In Kotlin `if`, `when`, and `try` can be **expressions** that produce a value. The compiler must assign one static type to the whole thing, even though different branches may yield different types. ## The rule: least upper bound (LUB) The inferred type is the **least upper bound** of all branch types — the *most specific* type that is a supertype of every branch. 'Least' means it picks the tightest common type, not just `Any?`. ## Worked example with null ```kotlin val x = if (cond) "hi" else null ``` - Branch 1: `"hi"` has type `String`. - Branch 2: `null` has type `Nothing?` (the literal `null` is the sole value of `Nothing?`). Now compute `LUB(String, Nothing?)`: - `Nothing?` sits at the very bottom of the **nullable** hierarchy — it is a subtype of every `T?`. - In particular `Nothing? <: String?` and `String <: String?`. - The smallest type above *both* `String` and `Nothing?` is `String?`. So `x: String?`. ## Nullability unifies upward The key intuition: **if any branch can be null, the unified result is nullable.** Mixing a non-null branch with a nullable (or `null`) branch lifts the result into the nullable half of the lattice: ```kotlin val a = if (c) 1 else null // Int? val b = if (c) listOf(1) else null // List<Int>? ``` ## Mixing unrelated types If branches are unrelated, the LUB climbs higher: ```kotlin val y = if (c) "s" else 1 // common supertype: Any (conceptually Comparable<*> & Serializable) ``` And if either could be null, you'd get `Any?`. ## when and try behave identically ```kotlin val z = when (k) { 1 -> "one" else -> null } // z: String? ``` ## Why it matters This LUB-based unification is exactly how the nullable lattice shows up in everyday inference: a single nullable branch makes the whole expression nullable, which then forces null-safe handling downstream.

  • What is the type of the literal `null` on its own?
    Nothing? — the nullable form of the bottom type. It is a subtype of every nullable type, which is why it unifies with any T to give T?.
  • What type does `if (c) "s" else 1` infer?
    A common supertype of String and Int. Conceptually Any; in practice the compiler computes an intersection of shared supertypes (Comparable<*> & Serializable), still non-null since neither branch is null.

Like finding the lowest common manager two employees both report to — you climb the org chart only as high as needed.

saying these in an interview costs you the question

  • Saying the result is Any? when both branches share a concrete supertype like String?
  • Claiming if/else with mismatched branch types is a compile error in expression position
  • Not knowing null's type is Nothing?
  • Thinking the result stays non-null even when a branch returns null

context