Scope Functions in Kotlin

Scope Functions in Kotlin

Kotlin has many language benefits over Java, which is often criticized for being too verbose. On Android, you’re stuck with using Java 8, so all the improvements to Java that have come since aren’t available to you. Hopefully you’ve heard of Kotlin by now, and hopefully given it a try. It has 100% interoperability with Java since it compiles down to the same byte code, so you can easily use both languages within the same project. Kotlin has many wonderful features that make Android programming much nicer – coroutines, extension functions, higher order functions, typealiases – the list goes on. But today I wanted to talk about Scope Functions. You might have seen these before and wondered what the difference between them is.

Here’s a list:

  • let
  • also
  • with
  • run
  • apply

So what’s the difference between these? When should you use them?


Let’s start with let, since this one is potentially the easiest to understand. Imagine you have an Optional value – String, Int, whatever. What let does is, when used on an optional value, the lambda’s context object will be the safely unwrapped value. That’s to say, if the value of the optional is null, the lambda won’t be executed.

var optionalString: String? = null
optionalString?.let { it.length }

The context object passed to the lambda – it – is the value of optionalString if it has one. The result of using let will be the lambda result.


Run is similar to let, with the main difference being that the context object passed to the lambda is available as a receiver – that is to say, within the lambda, this is whatever object you’ve executed run on. If that’s a bit confusing, don’t worry – it is. But here’s a little example which should hopefully clear some questions.

var myObject = MyObject()
myObject.let { it.doSomething() } { doSomething() }

Notice how with run I haven’t had to use it to call doSomething(). That’s because the context this is MyObject. The benefit of this is that if you only care about MyObject and you’re calling a bunch of functions on it within the lambda, you don’t have to call it or give the passed in value a name. The issue with this is if you want to use some other context within your lambda, you lose the ability to reference that. You’ll probably run into this most commonly when you’re in an Activity or Fragment and want to get context, within a run lambda you don’t have the correct context to get it, but within a let lambda you do.

Another great thing about using run is as a non-extension function. So for example, imagine you want to create a variable that isn’t just a single line assignment, but instead you want the value of the variable to be the result of a series of expressions. You could use by lazy, but this means the expressions will only be executed the first time the variable is accessed. This might be what you want, but it also might not be. Using run allows you to do this in a non-lazy way.

val calculation: Calculation = run {    
    val firstNumber = 4 * 5
    val secondNumber = 8   4
    Calculation(firstNumber, secondNumber)  

Now, when the class that holds this variable is initialized, the run block will be executed and the result will be assigned.


with is slightly different from run and let. with can’t be used as an extension on an object, and so instead you use it like using any other function. You pass it an object, and then within the lambda, the context is the object you passed in. Like run and let, with returns the lambda result. As an example, let’s say you have a WebView subclass, and within the initializer, you want to set a bunch of things on the settings object. You could do this:


But when using with, this can be simplfied to the following:

with(settings) {

When you’re setting a lot of things, or doing a lot of things with an object, this can make the code much easier to read, in addition to not having to write settings. before everything.


apply is an extension function which passes the object it was called on as the context to the lambda. The return value of using apply is the object itself, not the result of the lambda. It’s easiest to demonstrate this with a small code example.

val myFragment: MyFragment = MyFragment()
myFragment.something = true
myFragment.anotherThing = “eggs”
myFragment.somethingElse = 50

This can be rewritten to the following:

val myFragment: MyFragment = MyFragment().apply {
    something = true
    anotherThings = “eggs”
    somethingElse = 50


The final scope function is also. also can be thought of as a way to perform a side effect, rather than modifying an object like the scope functions. So for example, let’s say you want to log something before you change it and then immediately after you’ve changed it. The traditional Java approach would be something like this

val numbers = mutableListOf(“one”, “two”)
Log.d(TAG, numbers.size.toString())
Log.d(TAG, numbers.size.toString())

With also, this can be rewritten as:

val numbers = mutableListOf(“one”, “two”)
    .also { Log.d(TAG, it.size.toString() }
    .also { Log.d(TAG, it.size.toString() }

It’s a small change in this example, but can be used to make code a lot more readable. You could, for example, make it so that any time a variable is accessed, an also block is executed that logs its value, for example.

Hopefully this article helps clear up any confusion you have about the various scope functions and what each of them do. In addition to this, the official Kotlin docs has a very useful table that breaks down these scope functions, just in case you briefly forget what a particular one does.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s