Intermediate 9 min readKotlin 2.0

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

Scope functions execute a block of code in the context of an object. They differ in how they refer to the object (it or this) and what they return.

What You Will Learn

  • What each scope function does
  • Context object: it vs this
  • Return value: last expression vs the object
  • Practical use cases for each
  • Chaining scope functions

Overview

| Function | Context | Returns | Use case | |---|---|---|---| | let | it | Lambda result | Nullable check, transform | | run | this | Lambda result | Object config + compute result | | with | this | Lambda result | Multiple calls on same object | | apply | this | The object | Object configuration/builder | | also | it | The object | Side effects without changing chain |

let — transform or null-check

let executes a block with the object as "it". Returns the lambda result.

let

kotlin
fun main() {
    val name: String? = "kotlin"
    val length = name?.let {
        println("Processing: $it")
        it.length
    }
    println(length)

    val result = "  hello  ".let { it.trim().uppercase() }
    println(result)
}
Output
Processing: kotlin 6 HELLO

name?.let { } only runs when name is not null. The block receives name as "it". The last expression (it.length) is returned and stored in length.

Best Practice: Use let for nullable safety: value?.let { doSomethingWith(it) }

apply — configure an object

apply runs a block with the object as "this". Returns the object itself.

apply

kotlin
data class Config(var host: String = "", var port: Int = 80, var debug: Boolean = false)

fun main() {
    val config = Config().apply {
        host = "localhost"
        port = 8080
        debug = true
    }
    println(config)
}
Output
Config(host=localhost, port=8080, debug=true)

apply is perfect for object builders. The block runs with the Config instance as "this", so you can set properties without repeating the variable name.

also — side effects

also runs a block with the object as "it". Returns the object unchanged.

also

kotlin
fun main() {
    val numbers = mutableListOf(1, 2, 3)
        .also { println("Initial: $it") }
        .also { it.add(4) }
        .also { println("After add: $it") }
    println(numbers)
}
Output
Initial: [1, 2, 3] After add: [1, 2, 3, 4] [1, 2, 3, 4]

also returns the original object so you can chain calls. It is useful for logging or validation without breaking the call chain.

with — multiple calls on an object

with takes an object as a regular argument. "this" is the object inside the block.

with

kotlin
data class User(val name: String, val email: String, val age: Int)

fun main() {
    val user = User("Juned","juned@example.com",25)
    val info = with(user) {
        "Name: $name | Email: $email | Age: $age"
    }
    println(info)
}
Output
Name: Juned | Email: juned@example.com | Age: 25

with(user) { } makes user the implicit "this" inside the block. You can call user's properties without the object prefix.

Practice Exercise

Exercisemultiple choice

Which scope function returns the original object (not the lambda result)?

Quick Quiz

Quick Quiz

In let, how do you refer to the context object?

Frequently Asked Questions

Related Tutorials

Last updated: 2026-05-01Kotlin 2.0

Written by KotlinGuide Editorial Team · Reviewed by KotlinGuide Technical Review