Kotlin Safe Call Operator

The safe call operator ?. lets you access properties or call methods on a nullable variable without crashing. If the variable is null, the expression returns null instead of throwing an error.

The Problem Without Safe Call

var name: String? = null
println(name.length)   // ERROR at compile time — cannot access on nullable

Using the Safe Call Operator

var name: String? = null
println(name?.length)   // null — no crash

name = "Kotlin"
println(name?.length)   // 6

How ?. Works


name?.length

         Is name null?
              │
       ┌──────┴──────┐
      YES            NO
       │              │
   return null    access .length
                  return the value

Chaining Safe Calls

data class Address(val city: String?)
data class User(val name: String, val address: Address?)

val user: User? = User("Ana", Address(null))

// Each ?. protects the next step
println(user?.address?.city)    // null (city is null)
println(user?.name)             // Ana

Safe Call with Function

val text: String? = "  hello world  "

println(text?.trim())           // "hello world"
println(text?.uppercase())      // "  HELLO WORLD  "
println(text?.split(" ")?.size) // 3

val nullText: String? = null
println(nullText?.trim())       // null (no crash)

Safe Call in Assignment

val cityName: String? = user?.address?.city

if (cityName != null) {
    println("City: $cityName")
} else {
    println("City not available")
}

Safe Call with let

Combine ?. with let to run a block only when the value is not null:

val email: String? = "user@example.com"

email?.let {
    println("Sending email to: $it")
    println("Domain: ${it.substringAfter("@")}")
}

val noEmail: String? = null
noEmail?.let {
    println("This block never runs")
}
// Nothing printed for noEmail

Practical Example: Order Tracking

data class TrackingInfo(val status: String, val location: String?)
data class Order(val id: String, val tracking: TrackingInfo?)

fun main() {
    val order1 = Order("ORD-001", TrackingInfo("In Transit", "Mumbai Depot"))
    val order2 = Order("ORD-002", TrackingInfo("Processing", null))
    val order3 = Order("ORD-003", null)

    val orders = listOf(order1, order2, order3)

    for (order in orders) {
        val status   = order.tracking?.status   ?: "No update"
        val location = order.tracking?.location ?: "Location unknown"
        println("${order.id}: $status @ $location")
    }
}

Output:

ORD-001: In Transit @ Mumbai Depot
ORD-002: Processing @ Location unknown
ORD-003: No update @ Location unknown

Leave a Comment

Your email address will not be published. Required fields are marked *