Kotlin let with Null

The let scope function runs a code block and passes the calling object as a parameter named it. Combined with the safe call operator ?., it creates a clean pattern for executing code only when a value is not null.

let Syntax

value?.let {
    // this block runs only if value is NOT null
    // 'it' refers to the non-null value inside this block
    println(it)
}

Without let vs With let

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

// Without let
if (email != null) {
    println("Sending to: ${email.uppercase()}")
    println("Domain: ${email.substringAfter("@")}")
}

// With let — same result, more expressive
email?.let {
    println("Sending to: ${it.uppercase()}")
    println("Domain: ${it.substringAfter("@")}")
}

How ?. let Works


email?.let { ... }
   │
   └── Is email null?
          │
     ┌────┴────┐
    YES        NO
     │          │
   skip       run the block
   block    with it = email (non-null String)

let Returns a Value

let returns the last expression in its block. Combine this with ?: to handle the null case too:

val name: String? = "ravi kumar"

val displayName = name?.let {
    it.split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { c -> c.uppercase() }
    }
} ?: "Anonymous"

println(displayName)   // Ravi Kumar

val nullName: String? = null
val display2 = nullName?.let { it.uppercase() } ?: "Anonymous"
println(display2)      // Anonymous

let vs if-null-check


Use if when:
  You need else (handle both null and non-null cases)
  val result = if (x != null) x.process() else defaultValue

Use let when:
  You only care about the non-null case
  x?.let { doSomethingWith(it) }

Use let + ?: when:
  You need both cases but prefer concise style
  val result = x?.let { it.process() } ?: defaultValue

Renaming it Inside let

When nesting let calls, rename the parameter to avoid confusion:

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

user?.let { u ->
    email?.let { e ->
        println("Sending message to $u at $e")
    }
}

let for Side Effects

fun saveToDatabase(value: String) {
    println("Saved: $value")
}

val input: String? = "New record"

input?.let { saveToDatabase(it) }
// Saved: New record

val nothing: String? = null
nothing?.let { saveToDatabase(it) }
// (nothing happens, saveToDatabase is not called)

Practical Example: Notification Sender

data class UserPrefs(val pushToken: String?, val emailAddress: String?)

fun sendPush(token: String, message: String) {
    println("PUSH → $token : $message")
}

fun sendEmail(address: String, message: String) {
    println("EMAIL → $address : $message")
}

fun notify(prefs: UserPrefs, message: String) {
    prefs.pushToken?.let { sendPush(it, message) }
    prefs.emailAddress?.let { sendEmail(it, message) }
}

fun main() {
    val user1 = UserPrefs("device_abc_123", "alice@mail.com")
    val user2 = UserPrefs(null, "bob@mail.com")
    val user3 = UserPrefs("device_xyz_789", null)

    notify(user1, "Your order has shipped!")
    println()
    notify(user2, "Your order has shipped!")
    println()
    notify(user3, "Your order has shipped!")
}

Output:

PUSH → device_abc_123 : Your order has shipped!
EMAIL → alice@mail.com : Your order has shipped!

EMAIL → bob@mail.com : Your order has shipped!

PUSH → device_xyz_789 : Your order has shipped!

Leave a Comment

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