Cheat Sheet included

Kotlin Scope Functions Explained

Michal Ankiersztajn
3 min readMay 13, 2024

For those who have general knowledge but need a summary, here’s a Scope Functions Cheat Sheet:

Scope Functions Cheat Sheet

Now let’s get into details and examples:

Let

  • Reference: it
  • Returns: lambda result
  • Usage: executing code on a non-nullable object

To understand it better, we’ll use a helper function:

fun nonNullablePrint(text: String) {
println(text)
}

It’s a version of println that requires the String to be non-nullable.

Since it’s primarily used for safe access to nullable objects, it’s what we’ll do in the example:

class LetExample {
private var text: String? = null

fun printText() {
if (text != null) {
// text in reality still has a nullable scope here:
nonNullablePrint(text) // compilation error
}
}
}

Even though we’ve checked whether text is nullable, we still get a Compilation Error. Why? Because text is a mutable property and could’ve changed after the if check.

We can fix it using let that changes the scope to the current value of text instead of the variable itself:

class LetExample {
private var text: String? = null

fun printText() {
text?.let { nonNullablePrint(it) } // 100% sure it's non-nullable
}
}

With

  • Reference: this
  • Returns: lambda result
  • Usage: multiple function calls on an object

It stands for ‘with this we can do that’. Let’s take a look at the example:

fun noWithExample(list: List<Int>) {
println(list.size)
println(list.sum())
println(list.lastIndex)
println(list.toString())
}

The code can be improved: ‘with list we can print its info’:

fun withExample(list: List<Int>) {
with(list) {
println(size)
println(sum())
println(lastIndex)
println(toString())
}
}

Actually, in this example, we can reduce indentation by moving with the call since it returns lambda result :

fun betterWithExample(list: List<Int>) = with(list) {
println(size)
println(sum())
println(lastIndex)
println(toString())
}

It’ll return the same result as withExample

Run

  • Reference: this or -
  • Returns: lambda result
  • Usage: object configuration, but with the computed result

It works like a hybrid between with and let . Allowing you to run multiple functions on this object, but being called as an extension function like let :

class Service(
var address: String = "",
var portNumber: Int = 0,
) {
fun get() = "Getting from $address and $portNumber"
}

fun noRunExample(service: Service) {
service.address = "address.example"
service.portNumber = 8080
val result = service.get()
println(result)
}

We can modify it to use run :

fun runExample(service: Service) {
val result: String = service.run {
address = "address.example"
portNumber = 8080
get()
}
println(result)
}

It makes the code a bit cleaner. Moreover, it can be used without being called on an object. This way, we can limit the visibility of the Service if it’s a temporary object required to produce a result:

fun runTemporaryExample() {
val result: String = run {
val address = "address.example"
val port = 8080
Service(address, port).get()
}
address // We can't access this val
port // We can't access this val
println(result)
}

Also

  • Reference: it
  • Returns: object
  • Usage: additional effects

Let’s say we’re having an adding function. We want to print the produced result each time addition is being done:

fun addNoAlso(a: Int, b: Int): Int {
val result = a + b
println(result)
return result
}

The code is a bit lengthy. We could shorten it with also :

fun addAlso(a: Int, b: Int): Int = (a + b).also { println(it) }

We end up with 1-liner function!

Apply

  • Reference: this
  • Returns: object
  • Usage: object configuration

We can translate its usage to ‘apply this to the object’. Let’s say we have a Car class, we want to configure with a function:

class Car {
lateinit var name: String
lateinit var brand: String
}

fun configureCarNoApply() {
val car = Car()
car.name = "Super Car"
car.brand = "IT Cars"
}

We can replace it using apply function for easier access to Car members:

fun configureCarApply() {
val car: Car = Car().apply {
name = "Super Car"
brand = "IT Cars"
}
}

It works well, especially when we need to configure many fields!

Thanks for reading! If you’ve learned something, please clap and follow me for more!

Based on:

--

--

Michal Ankiersztajn

Android/Kotlin Developer & Applied Computer Science Engineer