Kotlin Scope Functions
Scope functions execute a block of code within the context of an object. They do not add new behavior — they improve how you write code that uses an object. Kotlin has five scope functions: let, run, with, apply, and also.
Why Scope Functions Exist
Without scope functions you often repeat the object's name multiple times:
val user = User() user.name = "Riya" user.age = 25 user.city = "Mumbai" println(user)
With apply, you write it once and configure inside a block:
val user = User().apply {
name = "Riya"
age = 25
city = "Mumbai"
}
println(user)
The Five Scope Functions at a Glance
Function | Object ref | Returns | Common use ---------|-------------|----------------|------------------------------- let | it | Lambda result | Null check + transform run | this | Lambda result | Compute a value using the object with | this | Lambda result | Operations on an object (non-null) apply | this | The object | Configure/initialize an object also | it | The object | Side effects (logging, validation)
let — Transform and Null-Check
Use let to work with a value, especially nullable ones. The object is referred to as it inside the block. The block's last expression is returned.
val name: String? = "Kotlin"
val result = name?.let {
println("Processing: $it")
it.uppercase()
}
println(result) // KOTLIN
val nullName: String? = null
val r2 = nullName?.let { it.uppercase() }
println(r2) // null (block never ran)
run — Compute a Result Using the Object
run combines object setup and result computation in one block. The object is this inside the block.
data class Circle(val radius: Double)
val area = Circle(5.0).run {
val pi = 3.14159
pi * radius * radius // last expression is returned
}
println(area) // 78.53975
run also works without a receiver — use it to create an isolated scope for a multi-step calculation:
val total = run {
val tax = 200.0
val base = 1000.0
base + tax // returns 1200.0
}
println(total) // 1200.0
with — Work With an Object You Already Have
with is a regular function (not an extension). Pass the object as an argument and work with it as this inside the block.
data class Printer(val brand: String, val model: String, var ink: Int)
val printer = Printer("HP", "LaserJet", 80)
with(printer) {
println("Brand: $brand")
println("Model: $model")
println("Ink: $ink%")
}
apply — Configure an Object
apply runs a setup block on an object and returns the same object. The object is this inside the block. Use it to initialize or configure an object just after creating it.
data class Config(var host: String = "", var port: Int = 0, var secure: Boolean = false)
val config = Config().apply {
host = "api.estudy247.com"
port = 443
secure = true
}
println(config)
// Config(host=api.estudy247.com, port=443, secure=true)
apply Chaining
val message = StringBuilder()
.apply { append("Hello") }
.apply { append(", ") }
.apply { append("World!") }
.toString()
println(message) // Hello, World!
also — Side Effects Without Changing Flow
also runs a block and returns the original object unchanged. The object is it inside the block. Use it for logging, validation, or debugging without breaking a chain of operations.
val scores = mutableListOf(85, 92, 78)
.also { println("Before sort: $it") }
.sorted()
.also { println("After sort: $it") }
// Before sort: [85, 92, 78]
// After sort: [78, 85, 92]
Choosing the Right Scope Function
Question | Use ------------------------------------------|------- Do a null-check and transform the value? | let Configure a new object during creation? | apply Compute a result from an object? | run Log or validate without changing flow? | also Call multiple methods on a non-null obj? | with
Diagram — return value and object reference
Object reference Returns
inside block
┌──────────────────────────────┐
let │ it (lambda param) │ Lambda result
run │ this (receiver) │ Lambda result
with │ this (argument) │ Lambda result
apply │ this (receiver) │ The object itself
also │ it (lambda param) │ The object itself
└──────────────────────────────┘
Real-World Example — Building an Android Intent
// With apply, Android setup code becomes clean and readable
val intent = Intent(context, DetailActivity::class.java).apply {
putExtra("userId", 42)
putExtra("screen", "profile")
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
}
startActivity(intent)
