Every app makes decisions — show this screen or that one, display an error or a success message, charge a discount or full price. In Kotlin, if and when are the tools for making those decisions. What makes Kotlin special is that both of these are expressions — they return a value, making your code cleaner and more concise. This guide covers everything with practical examples.


if — The Basic Conditional

The if statement works the same way as in most languages — run a block of code only if a condition is true.

val age = 20

if (age >= 18) {
    println("You are an adult")
}

Add else to handle the false case:

val age = 15

if (age >= 18) {
    println("You are an adult")
} else {
    println("You are a minor")
}

Chain multiple conditions with else if:

val score = 75

if (score >= 90) {
    println("Grade: A")
} else if (score >= 80) {
    println("Grade: B")
} else if (score >= 70) {
    println("Grade: C")
} else if (score >= 60) {
    println("Grade: D")
} else {
    println("Grade: F")
}

if as an Expression — Returns a Value

This is where Kotlin differs from Java. In Kotlin, if is an expression — it returns a value. This means you can assign the result of an if directly to a variable.

val age = 20

// if as an expression
val status = if (age >= 18) "Adult" else "Minor"

println(status)  // Adult

No need for a ternary operator (? :) like in Java — Kotlin's if expression does the same thing more readably.

Java ternary:

String status = age >= 18 ? "Adult" : "Minor";

Kotlin if expression:

val status = if (age >= 18) "Adult" else "Minor"

Multi-line if expression:

When the blocks are longer, use curly braces. The last expression in each block is the return value:

val score = 85

val grade = if (score >= 90) {
    println("Excellent!")
    "A"                      // this is returned
} else if (score >= 80) {
    println("Great job!")
    "B"                      // this is returned
} else if (score >= 70) {
    "C"
} else {
    "F"
}

println("Grade: $grade")  // Grade: B

Important: When using if as an expression that returns a value, the else branch is mandatory. Otherwise Kotlin doesn't know what to return if no condition matches.

// ❌ Error — else is required when if is used as expression
val result = if (x > 0) "Positive"

// ✅ Correct
val result = if (x > 0) "Positive" else "Non-positive"

Practical Android if Examples

Showing/hiding views based on state

fun updateUI(isLoading: Boolean, hasError: Boolean) {
    progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
    errorView.visibility = if (hasError) View.VISIBLE else View.GONE
    contentView.visibility = if (!isLoading && !hasError) View.VISIBLE else View.GONE
}

Setting text based on condition

fun bindUser(user: User) {
    nameTextView.text = user.name
    statusTextView.text = if (user.isOnline) "Online" else "Offline"
    statusTextView.setTextColor(
        if (user.isOnline) Color.GREEN else Color.GRAY
    )
    premiumBadge.visibility = if (user.isPremium) View.VISIBLE else View.GONE
}

Calculating values conditionally

fun calculateShipping(orderTotal: Double, isPremiumMember: Boolean): Double {
    return if (isPremiumMember) {
        0.0   // free shipping for premium
    } else if (orderTotal >= 50.0) {
        0.0   // free shipping over $50
    } else {
        4.99  // standard shipping fee
    }
}

when — Kotlin's Powerful Switch

when replaces Java's switch statement, but it's far more powerful. It can match against values, ranges, types, and conditions — and like if, it's an expression that returns a value.

Basic when

val day = 3

when (day) {
    1 -> println("Monday")
    2 -> println("Tuesday")
    3 -> println("Wednesday")
    4 -> println("Thursday")
    5 -> println("Friday")
    6 -> println("Saturday")
    7 -> println("Sunday")
    else -> println("Invalid day")
}

No break needed — Kotlin doesn't fall through to the next case automatically.

when as an Expression

val day = 3

val dayName = when (day) {
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"
    7 -> "Sunday"
    else -> "Invalid day"
}

println(dayName)  // Wednesday

Multiple Values per Branch

val day = "Saturday"

val type = when (day) {
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday"
    "Saturday", "Sunday" -> "Weekend"
    else -> "Unknown"
}

println(type)  // Weekend

Ranges in when

val score = 85

val grade = when (score) {
    in 90..100 -> "A"
    in 80..89  -> "B"
    in 70..79  -> "C"
    in 60..69  -> "D"
    else       -> "F"
}

println(grade)  // B

when Without an Argument — Replaces if-else chains

When you don't pass an argument to when, each branch becomes a boolean condition. This is a clean replacement for long if-else if chains:

val temperature = 35

val weather = when {
    temperature < 0  -> "Freezing"
    temperature < 10 -> "Very Cold"
    temperature < 20 -> "Cold"
    temperature < 30 -> "Warm"
    temperature < 40 -> "Hot"
    else             -> "Extremely Hot"
}

println(weather)  // Hot

Type Checking with when

fun describe(obj: Any): String {
    return when (obj) {
        is Int    -> "Integer: $obj"
        is String -> "String of length ${obj.length}"
        is Boolean -> "Boolean: $obj"
        is List<*> -> "List with ${obj.size} elements"
        else      -> "Unknown type"
    }
}

println(describe(42))           // Integer: 42
println(describe("Hello"))      // String of length 5
println(describe(true))         // Boolean: true
println(describe(listOf(1,2)))  // List with 2 elements

Notice the smart cast — inside is String branch, obj is automatically treated as a String, so you can call obj.length without casting.

when with Sealed Classes — The Real Power

when truly shines when used with sealed classes. You get exhaustive checking — the compiler warns you if you miss a case.

sealed class NetworkResult {
    data class Success(val data: String) : NetworkResult()
    data class Error(val message: String) : NetworkResult()
    object Loading : NetworkResult()
}

fun handleResult(result: NetworkResult) {
    when (result) {
        is NetworkResult.Success -> {
            println("Data: ${result.data}")
        }
        is NetworkResult.Error -> {
            println("Error: ${result.message}")
        }
        is NetworkResult.Loading -> {
            println("Loading...")
        }
        // No else needed — sealed class covers all cases
        // Compiler warns if you miss a case
    }
}

when with return value and sealed class — real Android ViewModel

sealed class UiState {
    object Loading : UiState()
    data class Success(val articles: List<Article>) : UiState()
    data class Error(val message: String) : UiState()
    object Empty : UiState()
}

// In Fragment
private fun observeUiState(state: UiState) {
    when (state) {
        is UiState.Loading -> {
            progressBar.visibility = View.VISIBLE
            recyclerView.visibility = View.GONE
            errorView.visibility = View.GONE
        }
        is UiState.Success -> {
            progressBar.visibility = View.GONE
            recyclerView.visibility = View.VISIBLE
            errorView.visibility = View.GONE
            adapter.submitList(state.articles)
        }
        is UiState.Error -> {
            progressBar.visibility = View.GONE
            recyclerView.visibility = View.GONE
            errorView.visibility = View.VISIBLE
            errorTextView.text = state.message
        }
        is UiState.Empty -> {
            progressBar.visibility = View.GONE
            recyclerView.visibility = View.GONE
            emptyView.visibility = View.VISIBLE
        }
    }
}

Nested Conditions

Sometimes you need conditions inside conditions. Keep them readable:

val age = 25
val hasLicense = true
val hasInsurance = false

val canDrive = if (age >= 16) {
    if (hasLicense) {
        if (hasInsurance) {
            "Can drive"
        } else {
            "Need insurance"
        }
    } else {
        "Need a license"
    }
} else {
    "Too young to drive"
}

println(canDrive)  // Need insurance

But deeply nested conditions get hard to read. Prefer using when or logical operators:

// Cleaner version
val canDrive = when {
    age < 16            -> "Too young to drive"
    !hasLicense         -> "Need a license"
    !hasInsurance       -> "Need insurance"
    else                -> "Can drive"
}

if and when — Comparison

Feature if when
Basic condition
Multiple branches With else if Cleaner with ->
Range matching With && operators in 1..10 ->
Type checking With is is Type -> built-in
No argument (any condition) Natural when { }
Sealed class Manually Exhaustive check
Returns a value

Use if when:

  • You have a simple true/false condition
  • You have just 2-3 branches
  • The condition is a complex boolean expression

Use when when:

  • You have 3+ branches
  • You're matching against a specific value
  • You're checking types
  • You're using sealed classes
  • You want cleaner, more readable code

Common Mistakes to Avoid

Mistake 1: Forgetting else in if expression

// ❌ Error — else required when used as expression
val message = if (isLoggedIn) "Welcome back!"

// ✅ Correct
val message = if (isLoggedIn) "Welcome back!" else "Please log in"

Mistake 2: Using if-else chain when when is cleaner

// ❌ Hard to read
val label = if (status == "active") "Active"
    else if (status == "inactive") "Inactive"
    else if (status == "pending") "Pending"
    else if (status == "banned") "Banned"
    else "Unknown"

// ✅ Much cleaner
val label = when (status) {
    "active"   -> "Active"
    "inactive" -> "Inactive"
    "pending"  -> "Pending"
    "banned"   -> "Banned"
    else       -> "Unknown"
}

Mistake 3: Not using when for type checking

// ❌ Verbose
if (result is Success) {
    handleSuccess(result as Success)
} else if (result is Error) {
    handleError(result as Error)
}

// ✅ Clean — smart cast handles the casting
when (result) {
    is Success -> handleSuccess(result)   // result is auto-cast to Success
    is Error   -> handleError(result)     // result is auto-cast to Error
}

Mistake 4: Missing cases in when with sealed class

sealed class Shape {
    class Circle(val radius: Double) : Shape()
    class Rectangle(val width: Double, val height: Double) : Shape()
    class Triangle(val base: Double, val height: Double) : Shape()
}

fun area(shape: Shape): Double {
    return when (shape) {
        is Shape.Circle    -> Math.PI * shape.radius * shape.radius
        is Shape.Rectangle -> shape.width * shape.height
        // ❌ Missing Triangle — compiler will warn you!
        // ✅ Add: is Shape.Triangle -> 0.5 * shape.base * shape.height
    }
}

Summary

  • if in Kotlin is an expression — it returns a value, so you can assign it directly to a variable
  • Always include else when using if as an expression
  • when is a powerful replacement for switch that supports values, ranges, types, and conditions
  • when without an argument replaces long if-else if chains cleanly
  • Use when with sealed classes for exhaustive, compiler-checked branching
  • Smart cast works inside when branches — no manual casting needed
  • Prefer when over long if-else if chains for 3 or more conditions

Mastering if and when expressions is one of the quickest ways to write more idiomatic, readable Kotlin code.

Happy coding!