kotlinbeginner

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

Master Kotlin scope functions: when to use let, run, apply, also, and with for concise code.

kotlin
data class Config(
    var host: String = "localhost",
    var port: Int = 8080,
    var debug: Boolean = false,
    var tags: MutableList<String> = mutableListOf()
)

fun main() {
    // let — transform nullable, return result
    val name: String? = "Alice"
    val greeting = name?.let { "Hello, $it!" } ?: "Hello, stranger!"
    println(greeting) // Hello, Alice!

    // let — scoped variable
    val numbers = listOf(1, 2, 3, 4, 5)
    numbers.filter { it > 2 }
        .let { filtered ->
            println("Filtered: $filtered, Sum: ${filtered.sum()}")
        }

    // apply — configure object, return object itself
    val config = Config().apply {
        host = "prod.example.com"
        port = 443
        debug = false
        tags.add("production")
    }
    println(config)

    // also — side effects, return object
    val user = createUser("Bob")
        .also { println("Created user: ${it.first}") }
        .also { logUser(it) }
    println("User: $user")

    // run — block on object, return result
    val isSecure = config.run {
        port == 443 && !debug
    }
    println("Secure: $isSecure") // true

    // run — without receiver (scoped computation)
    val serverUrl = run {
        val protocol = if (config.port == 443) "https" else "http"
        "$protocol://${config.host}:${config.port}"
    }
    println(serverUrl)

    // with — call methods on object
    val info = with(config) {
        "Server at $host:$port (debug=$debug)"
    }
    println(info)

    // Chaining pattern
    val result = StringBuilder()
        .apply { append("Hello") }
        .apply { append(", ") }
        .apply { append("World!") }
        .toString()
    println(result)

    // Decision guide:
    // ┌──────────┬──────────────┬───────────────┐
    // │ Function │ Object ref   │ Return value  │
    // ├──────────┼──────────────┼───────────────┤
    // │ let      │ it           │ Lambda result │
    // │ run      │ this         │ Lambda result │
    // │ with     │ this         │ Lambda result │
    // │ apply    │ this         │ Object itself │
    // │ also     │ it           │ Object itself │
    // └──────────┴──────────────┴───────────────┘
}

fun createUser(name: String) = Pair(name, "${name.lowercase()}@test.com")
fun logUser(user: Pair<String, String>) { /* log */ }

Use Cases

  • Object initialization and configuration
  • Null-safe transformations with let
  • Logging and debugging side effects with also

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.