Scope-Limited Processing with let
| Since: | Kotlin 1.0(2016) |
|---|
In Kotlin, ?.let is a pattern that executes a lambda only when a nullable value is non-null. It lets you combine a null check and an action in a single expression. Inside the lambda, you can access the non-null value via the implicit parameter it.
Syntax
val name: String? = "sample_data"
name?.let {
println(it.length) // Runs only if name is non-null.
}
// Capture the result (null if the value was null).
val result: Int? = name?.let { it.length * 2 }
// Comparison with an if null check
if (name != null) {
println(name.length) // Smart cast
}
// Equivalent using ?.let:
name?.let { println(it.length) }
Syntax overview
| Syntax | Description |
|---|---|
| value?.let { it } | Executes the lambda if value is non-null and returns the result; returns null otherwise. |
| value?.let { v -> } | Uses an explicit parameter name instead of the default it. |
| ?.let combined with ?: | Provides a default value when null (e.g., value?.let { ... } ?: default). |
| ?.also { it } | Similar to let, but returns the receiver itself instead of the lambda result. Useful for side effects. |
Sample code
sample_let_null_check.kt
data class User(val name: String, val email: String?)
fun findUser(id: Int): User? = if (id == 1) User("user_1", "user_1@wp-p.info") else null
fun main() {
// Basic ?.let usage
val name: String? = "Kotlin"
name?.let {
println("Length: ${it.length}") // Length: 6
println("Uppercase: ${it.uppercase()}") // Uppercase: KOTLIN
}
// Skipped when the value is null
val nullName: String? = null
nullName?.let { println("This will not run.") }
println("Skipped null.") // Skipped null.
// Capture the result
val length: Int? = name?.let { it.length * 2 }
println("Double the length: $length") // Double the length: 12
// Combine with ?: (Elvis operator)
val nullLength: Int = nullName?.let { it.length } ?: -1
println("Default when null: $nullLength") // -1
// Chain nested nullables
val user: User? = findUser(1)
user?.let { u ->
u.email?.let { email ->
println("Email: $email") // Email: user_1@wp-p.info
}
}
// findUser(99) returns null, so this is skipped
findUser(99)?.let { println("User: ${it.name}") }
println("Skipped non-existent user.")
// Process nullable elements in a list
val names: List<String?> = listOf("item_a", null, "item_b", null, "item_c")
names.forEach { it?.let { name -> println(name) } }
}
let_null_check.kt
kotlinc let_null_check.kt -include-runtime -d let_null_check.jar java -jar let_null_check.jar Length: 6 Uppercase: KOTLIN Skipped null. Double the length: 12 Default when null: -1 Email: user_1@wp-p.info Skipped non-existent user. item_a item_b item_c
Common Mistakes
Mistake 1: The return value of ?.let is a nullable type (e.g., Int?), so you cannot use it directly in arithmetic.
mistake_let_nullable_return.kt
fun main() {
val nullName: String? = null
val len = nullName?.let { it.length } // len is Int?
// println(len + 1) // Compile error: operator + is not defined for Int?
}
Use ?: to provide a default value and get a non-null type.
fix_let_nullable_return.kt
fun main() {
val nullName: String? = null
val safeLen: Int = nullName?.let { it.length } ?: 0
println("Length: $safeLen") // Length: 0
}
Mistake 2: Omitting the parameter name in nested ?.let calls causes multiple its to clash and makes the code hard to read.
mistake_let_it_nested.kt
fun main() {
val name: String? = "sample_data"
// Nested it clashes and is confusing
name?.let { it?.let { it.uppercase() } }
}
Give explicit parameter names when nesting lambdas.
fix_let_it_nested.kt
fun main() {
val name: String? = "sample_data"
name?.let { n -> println(n.uppercase()) }
}
Mistake 3: let returns the result of the last expression, while also returns the receiver itself. Mixing them up produces an unexpected type.
mistake_let_vs_also.kt
fun main() {
val name: String? = "item_a"
val result1 = name?.let { it.length } // Int? (transformed value)
val result2 = name?.also { it.length } // String? (original value, not transformed)
println("let: $result1") // let: 6
println("also: $result2") // also: item_a
}
The command looks like this:
kotlinc mistake_let_vs_also.kt -include-runtime -d mistake_let_vs_also.jar java -jar mistake_let_vs_also.jar let: 6 also: item_a
Notes
?.let is one of the most commonly used null-check patterns in Kotlin. It is equivalent to if (value != null) { ... }, but because it is a scope function, you can assign its result to a variable or chain it with other calls.
The parameter inside the lambda defaults to it, but giving it an explicit name — such as ?.let { user -> ... } — improves readability when lambdas are nested.
For the basics of nullable types, see Nullable types / ?. operator. For scope functions in general, see let.
If you find any errors or copyright issues, please contact us.