Skip to content
intermediate Phase 1 · Kotlin Foundations

Scope Functions

Use let, run, with, apply, and also for concise and readable code.

40m
2 problems
Topic Progress 0%

The Five Scope Functions

What Scope Functions Do

Scope functions execute a block of code within the context of an object. They reduce boilerplate by eliminating repetitive variable references. Kotlin provides five: let, run, with, apply, and also.

Reference: this vs it

Each scope function provides the object differently:

Function Object reference Return value
let it Lambda result
run this Lambda result
with this Lambda result
apply this The object itself
also it The object itself

let

let executes the block with it as the object. It returns the lambda result. Use it for null-safe calls and transformations:

val name: String? = "Kotlin"

// Transform the value only if non-null
val length = name?.let {
    println("Processing $it")
    it.length
}
println(length)  // 6

// Use with collections
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.let {
    println("List has ${it.size} elements")
    it.sum()
}
println(sum)  // 15

run

run executes the block with this as the object. It returns the lambda result. Use it when you need both object access and a computed result:

data class ServerConfig(val host: String, val port: Int)

val config = ServerConfig("localhost", 8080)
val urlString = config.run {
    "http://$host:$port"
}
println(urlString)  // http://localhost:8080

with

with is similar to run but is called differently. It takes the object as a parameter rather than being called on it:

val sb = StringBuilder()
with(sb) {
    append("Hello")
    append(", ")
    append("World")
}
println(sb.toString())  // Hello, World

Use with when you need to call multiple methods on the same object and the result is not needed.

apply

apply executes the block with this but returns the object itself (not the lambda result). It is the standard choice for object configuration:

data class User(var name: String = "", var age: Int = 0, var email: String = "")

val user = User().apply {
    name = "Alice"
    age = 30
    email = "alice@example.com"
}
println(user)  // User(name=Alice, age=30, email=alice@example.com)

// Builder pattern
val textView = TextView().apply {
    textSize = 16f
    setTextColor(Color.BLACK)
    text = "Hello"
}

apply is the most commonly used scope function for initialization.

also

also executes the block with it and returns the object itself. Use it for side effects that should not change the object:

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

Chaining Scope Functions

Scope functions can be chained for fluent object construction:

val person = Person()
    .apply {
        name = "Alice"
        age = 30
    }
    .also {
        println("Created: $it")
    }

Choosing the Right One

  • apply: Configure an object (returns the object)
  • also: Perform side effects on an object (returns the object)
  • let: Transform or null-check a value (returns the lambda result)
  • run: Configure and compute a result (returns the lambda result)
  • with: Call multiple methods on an object (returns the lambda result)

Practice Problems

0 / 3 solved
Object Configuration

Use scope functions to create a Person object, set its name and age with apply, log its creation with also, and compute a formatted string with run.

Solution
data class Person(var name: String = "", var age: Int = 0)

fun createPerson(): Person {
    return Person()
        .apply {
            name = "Alice"
            age = 30
        }
        .also {
            println("Created person: $it")
        }
        .run {
            "$name is $age years old"
        }
        .let { formatted ->
            println(formatted)
            Person().apply { name = "Alice"; age = 30 }
        }
}

fun main() {
    createPerson()
}
Null-Safe Transformation

Write a function that takes a nullable String? and uses let to return its length multiplied by 10 if non-null, or -1 if null.

Solution
fun processName(name: String?): Int {
    return name?.let {
        println("Processing: $it")
        it.length * 10
    } ?: -1
}

fun main() {
    println(processName("Kotlin"))  // Processing: Kotlin, 60
    println(processName(null))       // -1
}
Chained Configuration

Use scope functions to build a configuration map: start with an empty mutable map, use apply to add key-value pairs, use also to log the map size, and use let to format the map as a string.

Solution
fun buildConfig(): String {
    return mutableMapOf<String, String>()
        .apply {
            put("host", "localhost")
            put("port", "8080")
            put("env", "production")
        }
        .also {
            println("Config has ${it.size} entries")
        }
        .let { config ->
            config.entries.joinToString(", ") { "${it.key}=${it.value}" }
        }
}

fun main() {
    println(buildConfig())
    // Config has 3 entries
    // host=localhost, port=8080, env=production
}

Quiz

1. Which scope function returns the object itself (not the lambda result)?

Question 1 options

2. In which scope function is the object referenced as 'it' rather than 'this'?

Question 2 options

3. When should you use apply instead of let?

Question 3 options

4. What is the difference between run and with?

Question 4 options

Flashcards

Question

When should you use let vs apply?

Answer

let returns the lambda result (use for transformations). apply returns the object itself (use for configuration). let uses 'it'; apply uses 'this'.

Question

What is the most common use case for apply?

Answer

Object configuration and initialization. apply lets you set multiple properties on an object using this context, then returns the configured object for chaining.

Question

How does also differ from let?

Answer

Both use 'it' to reference the object. let returns the lambda result. also returns the object itself. Use also for side effects; use let for transformations.

Question

What does with(object) { } do?

Answer

Executes the block with the object as 'this' context. Returns the lambda result. Useful when calling multiple methods on the same object without needing the return value.

Revision Notes

Key Takeaways

  • 1. apply is the most common scope function for object configuration.
  • 2. let returns the lambda result; apply/also return the object.
  • 3. let/also use 'it'; run/with/apply use 'this'.
  • 4. Chain scope functions for fluent object construction.
  • 5. Choose based on what you need: the object back or a computed result.

Interview Tips

  • Know when to use apply vs let with concrete examples.
  • Be ready to explain why scope functions improve readability.
  • Understand the difference between returning the object vs the lambda result.
  • Practice chaining scope functions for object initialization patterns.

Cheat Sheet

Scope Functions Cheat Sheet

let:

  • Reference: it
  • Returns: lambda result
  • Use: null-safe calls, transformations

run:

  • Reference: this
  • Returns: lambda result
  • Use: compute a result with object context

with:

  • Reference: this
  • Returns: lambda result
  • Use: call multiple methods on same object

apply:

  • Reference: this
  • Returns: the object itself
  • Use: object configuration (most common)

also:

  • Reference: it
  • Returns: the object itself
  • Use: side effects, logging

Decision Guide:

  • Need the object back? apply or also
  • Need a result? let, run, or with
  • Configuring? apply
  • Side effects? also
  • Transforming? let