let, also, apply, run, with — Kotlin Scope Functions: When to Use Which?

Blair Fernandes
3 min readMay 28, 2023
Photo by Arnold Zhou on Unsplash

Kotlin, with its powerful language features, offers a set of scope functions — let, also, apply, run, and with — that provide concise ways to perform operations on objects within a limited scope. In this post, we will explore each scope function, understand their differences, and learn when to use each of them.

1. let

The let function allows us to perform operations on an object and return a result. It is particularly useful when we need to perform a chain of operations on a nullable object or when we want to perform some additional computations with the object.

val nullableValue: String? = "Hello"
val result = nullableValue?.let { it ->
it.toUpperCase()
}.orEmpty()

The context object (the object on which you call the scope function) is available as an argument (it) in the lambda. The return value of the let function is the result of the lambda which in this case is the String converted to uppercase.

2. also

The also function allows us to perform additional operations on an object and return the same object. It is useful when we want to perform side effects on an object without modifying its properties.

val list = listOf<String>("Item 1", "Item 2").also{ it -> 
println("Returned list $it")
}

The also function is similar to the let function as it also receives the context object as an argument (it) in the lambda. The only difference here is that its return value is the original object on which it was called and not the result of the lambda.

3. apply

The apply function allows us to initialize properties of an object or configure its state in a builder-like manner. It is commonly used for object configuration during initialization.

val person = Person().apply {
name = "John Doe"
age = 30
say("Hello") //Same as this.say("Hello")
}

The apply function gets the context object as a receiver in the this reference. So you can access the context object’s properties and methods in the lambda with or without the this keyword. The return value of the apply function is the context object itself.

4. run

The run function is similar to apply but with a slight difference in the way it handles the object reference. run executes a block of code on an object and returns the result of the block. It is often used when we need to perform multiple operations on an object without the need to refer to the object explicitly.

val message = "Hello, World!"
val result = message.run {
val uppercased = toUpperCase() //Same as this.toUpperCase()
"$uppercased length: ${length}" //Result of the lambda
}

The run function also gets the context object as a receiver in the this reference. However its return value is the result of the lambda.

5. with

The with function is similar to run but without an explicit receiver object. Unlike run, with is not an extension function hence the context object has to be passed to it as an argument. It allows us to work with an object’s properties and functions directly without the need for a separate reference. with is useful when we want to perform operations on a specific object without creating a new scope.

val person = Person("John Doe", 30)
val result = with(person) {
val introduction = "My name is $name and I am $age years old."
introduction.length
}

Though the context object is passed as an argument to the with function, it is available in the lambda as a receiver in the this reference. Its return value is the result of the lambda.

Conclusion

Kotlin scope functions provide powerful tools for concise and expressive code. Though they may seem overlapping, let, also, apply, run, and with offer different capabilities and serve distinct purposes. By understanding the characteristics and use cases of each scope function, you can leverage them effectively in your Kotlin codebase.

Check Kotlin’s official documentation for more details.

--

--

Blair Fernandes

I am a passionate programmer 💻, geek 🤓 & an avid gamer🎮