In Kotlin, scope functions can be chained because each one returns either:
- the receiver object:
also,apply - the lambda result:
let,run,with
The key is understanding what each function returns.
Common chaining pattern
val result = User("Alice")
.also {
println("Created user: $it")
}
.apply {
name = name.uppercase()
}
.let {
"User name is ${it.name}"
}
println(result)
Here:
alsoreceives the object asitand returns the sameUserapplyreceives the object asthisand returns the sameUserletreceives the object asitand returns the lambda result, aString
Example with a data class
data class User(var name: String, var age: Int = 0)
val description = User("Alice")
.apply {
age = 30
}
.also {
println("Configured user: $it")
}
.run {
"$name is $age years old"
}
println(description)
Output:
Configured user: User(name=Alice, age=30)
Alice is 30 years old
Choosing the right scope function in a chain
| Function | Object reference | Returns | Common use |
|---|---|---|---|
let |
it |
lambda result | transform value, null checks |
run |
this |
lambda result | compute result from object |
with |
this |
lambda result | operate on existing object |
apply |
this |
receiver object | configure object |
also |
it |
receiver object | side effects like logging |
Nullable chaining
Scope functions are especially useful with nullable values:
val length = maybeName
?.trim()
?.takeIf { it.isNotEmpty() }
?.also {
println("Valid name: $it")
}
?.let {
it.length
}
Here, the chain stops if any step returns null.
Practical example
val user = User(" alice ")
.apply {
name = name.trim()
}
.also {
println("After trim: $it")
}
.apply {
name = name.replaceFirstChar { it.uppercase() }
}
.also {
println("Final user: $it")
}
Rule of thumb
Use:
apply { }
when you want to configure an object and keep chaining the same object.
Use:
also { }
when you want to perform a side effect and keep chaining the same object.
Use:
let { }
or:
run { }
when you want to transform the object into another result.
So a typical chain often looks like:
val result = createObject()
.apply {
// configure object
}
.also {
// log or validate
}
.let {
// transform into final result
}
