Kotlin Scope Functions

Scope functions — let, run, with, apply, and also — execute a block of code in the context of an object. They reduce repeated variable names and make setup and transformation chains more readable.

The Five Scope Functions at a Glance


Function │ Object inside block │ Returns      │ Use case
─────────┼─────────────────────┼──────────────┼───────────────────────
let      │ it                  │ block result │ null checks, transform
run      │ this                │ block result │ compute and return
with     │ this                │ block result │ operate on an object
apply    │ this                │ the object   │ configure/set up
also     │ it                  │ the object   │ side effects, logging

let — Transform and Check Null

val name: String? = "  alice  "

val result = name?.let {
    it.trim().capitalizeWords()
} ?: "Anonymous"

println(result)   // Alice

// Without let:
val result2 = if (name != null) name.trim() else "Anonymous"

run — Compute and Return

val config = run {
    val host = "localhost"
    val port = 8080
    "$host:$port"   // returned as the result
}

println(config)   // localhost:8080

with — Operate on an Object

data class Report(var title: String = "", var pages: Int = 0, var author: String = "")

val summary = with(Report()) {
    title = "Quarterly Review"
    pages = 24
    author = "Finance Team"
    "Report '$title' by $author ($pages pages)"  // returned
}

println(summary)   // Report 'Quarterly Review' by Finance Team (24 pages)

apply — Configure an Object

data class Notification(
    var title: String = "",
    var body: String = "",
    var priority: Int = 1,
    var soundEnabled: Boolean = true
)

val notification = Notification().apply {
    title = "New message"
    body = "You have 3 unread messages"
    priority = 2
    soundEnabled = false
}

println(notification)
// Notification(title=New message, body=You have 3 unread messages, priority=2, soundEnabled=false)

also — Side Effect Without Changing Object

val numbers = mutableListOf(1, 2, 3)
    .also { println("Before: $it") }

numbers.add(4)
numbers
    .also { println("After: $it") }
    .removeIf { it % 2 == 0 }
    .also { println("Odd only: $numbers") }

Chaining Scope Functions

data class User(var name: String, var email: String, var verified: Boolean = false)

fun createUser(name: String, email: String): User =
    User(name, email)
        .apply { verified = name.isNotBlank() && email.contains("@") }
        .also  { println("Created user: $it") }

fun main() {
    val user = createUser("Bob", "bob@mail.com")
    println("Verified: ${user.verified}")
}

Choosing the Right Function


You want to…                              Use
──────────────────────────────────────────────────────────
Run code only when not null               ?.let { }
Compute a value using multiple steps      run { }
Call many methods on an existing object   with(obj) { }
Set up properties on a new object         obj.apply { }
Log or inspect without changing return    obj.also { }

Practical Example: HTTP Request Builder

data class HttpRequest(
    var url: String = "",
    var method: String = "GET",
    var headers: MutableMap = mutableMapOf(),
    var body: String? = null
)

fun buildRequest(configure: HttpRequest.() -> Unit): HttpRequest =
    HttpRequest().apply(configure)

fun main() {
    val request = buildRequest {
        url = "https://api.example.com/users"
        method = "POST"
        headers["Authorization"] = "Bearer token123"
        headers["Content-Type"]  = "application/json"
        body = """{"name":"Alice","role":"admin"}"""
    }

    with(request) {
        println("$method $url")
        headers.forEach { (k, v) -> println("  $k: $v") }
        println("  Body: $body")
    }
}

Leave a Comment

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