Skip to content
AgileLogics
6 min read

Scope Functions: let, run, with, apply, also

Prerequisites: Extension functions, Lambdas and higher-order functions

Kotlin's standard library has five small functions that run a block of code on an object. They differ in two ways: how the block refers to the object (it or this), and what the function returns.

Function Object is Returns Typical use
let it the block result Run code on a non-null value; transform
run this the block result Compute a result from an object
with this the block result Call several methods on one object
apply this the object Configure an object
also it the object Side effects, such as logging

Examples

class Request {
    var url = ""
    var method = "GET"
    val headers = mutableMapOf<String, String>()
}

fun main() {
    // let: only when not null, and transform the value
    val email: String? = "Ada@Example.com"
    val domain = email?.let { it.substringAfter("@").lowercase() }
    println(domain)

    // apply: configure a new object, get the object back
    val request = Request().apply {
        url = "https://api.example.com/users"
        method = "POST"
        headers["Accept"] = "application/json"
    }
    println("${request.method} ${request.url}")

    // also: do something extra, keep the value flowing
    val numbers = mutableListOf(3, 1, 2)
        .also { println("Before sorting: $it") }
    numbers.sort()

    // with: several calls on one object, return a result
    val summary = with(numbers) {
        "size=$size, first=${first()}, last=${last()}"
    }
    println(summary)

    // run: compute a result from an object
    val total = request.run { headers.size + if (method == "POST") 1 else 0 }
    println(total)
}
example.com
POST https://api.example.com/users
Before sorting: [3, 1, 2]
size=3, first=1, last=3
2

Choosing

You'll see ?.let { } and apply { } most. Pick based on intent:

  • Configuring an object you just created → apply
  • Doing something with a value only if it isn't null → ?.let
  • Logging or checking a value in the middle of a chain → also

Common mistakes

  • Nesting scope functions several deep. When it and this start referring to different objects on nearby lines, plain local variables are clearer.
  • Using ?.let just to avoid an if. if (x != null) is often easier to read, and smart casts make x non-null inside it anyway.