Olson CloudWorks πŸš€

Example of when should we use run let apply also and with on Kotlin

September 19, 2026

πŸ“‚ Categories: Kotlin
🏷 Tags: Kotlin
Example of when should we use run let apply also and with on Kotlin

Kotlin, a modern and concise programming language, offers several scope functions that can make your code more readable and efficient. Understanding when to use run, let, apply, also, and with in Kotlin is crucial for writing clean and idiomatic code. Each of these functions serves a specific purpose, and choosing the right one can significantly improve the clarity and maintainability of your projects. This article will provide a comprehensive example of when should we use run, let, apply, also and with on Kotlin, complete with practical examples to guide you in making the best choices for your coding scenarios. We will cover each function in detail, exploring their unique characteristics and demonstrating how they can streamline your development process. Mastering these scope functions is a key step towards becoming a proficient Kotlin developer, allowing you to write more expressive and less error-prone code.

Understanding Kotlin Scope Functions

Kotlin scope functions are higher-order functions that allow you to execute a block of code within the context of an object. They provide a concise way to perform operations on an object without having to repeatedly refer to it by name. This enhances code readability and reduces boilerplate. Each scope function differs in how it makes the context object available inside the block and what it returns as a result. The key differences lie in whether the context object is available as this or it, and whether the function returns the context object itself or the result of the block. Understanding these nuances is essential for choosing the right scope function for a particular task. Knowing the nuances can improve code readability, a key aspect of maintainable code.

The primary goal of using scope functions is to make code more readable and concise. By leveraging these functions, developers can avoid repetitive code and create more expressive solutions. For instance, initializing objects or performing operations on nullable objects can be greatly simplified using scope functions. According to Kotlin documentation, “Scope functions can make your code more readable and concise by providing a way to perform operations on an object within its scope.” Kotlin Scope Functions Documentation provides more detailed explanations and examples. Understanding the distinct use cases for each function allows developers to write more maintainable and efficient code. Proper usage contributes to better code quality.

Choosing the correct scope function is not always straightforward. It depends on the specific task you’re trying to accomplish and the desired outcome. Should you modify the object directly, or do you need to return a different value? Do you want to refer to the object implicitly or explicitly? Answering these questions will guide you towards the appropriate scope function. The following sections will delve into each function in detail, providing clear examples and use cases for each.

Detailed Look at ’let'

The let function in Kotlin allows you to execute a block of code on a non-null object. The context object is available inside the block as it, and the function returns the result of the block. let is particularly useful for handling nullable objects and performing operations only when the object is not null. It provides a safe and concise way to avoid NullPointerExceptions. This function is a cornerstone of null-safe Kotlin programming. Here’s a featured snippet-optimized paragraph: let is used to execute a block of code on a non-null object. Access the object within the block using it, and let returns the result of the block. This is especially useful for safely handling nullable objects, preventing NullPointerExceptions by executing code only when the object is not null.

Consider this scenario: you have a nullable string and you want to print its length only if it’s not null. Using let, you can achieve this with a single line of code:

val name: String? = "Kotlin" name?.let { println("The length of the name is ${it.length}") } 

In this example, the code inside the let block will only execute if name is not null. Within the block, it refers to the non-null value of name. This approach eliminates the need for explicit null checks and makes the code more readable. let can also be chained for multiple operations on the same object. The safe call operator ?. ensures that let is only called when the object isn’t null.

Another common use case for let is transforming data. You can use let to transform an object and return a different value. For example:

val number: String? = "10" val intValue: Int? = number?.let { it.toInt() } println(intValue) // Output: 10 

Here, let is used to convert a nullable string to a nullable integer. If number is null, intValue will also be null. If number is not null, it will be converted to an integer and assigned to intValue. This demonstrates the versatility of let in handling nullable values and performing transformations. These features make let a powerful tool in Kotlin development.

Exploring ‘run’

The run function in Kotlin has two forms: one that operates on an object and one that operates as a standalone function. When called on an object, the context object is available inside the block as this, and the function returns the result of the block. When used as a standalone function, run simply executes the block of code and returns the result. The run function is best suited for executing a block of code that calculates and returns a value, or for configuring an object and returning a result based on the configuration.

When used on an object, run is similar to let but uses this instead of it to refer to the context object. This can make the code more readable when you’re performing multiple operations on the same object. Consider this example:

data class Person(var name: String, var age: Int) val person = Person("Alice", 30) val description = person.run { "Name: $name, Age: $age" } println(description) // Output: Name: Alice, Age: 30 

In this case, run allows you to access the properties of the person object directly using this (which is implicit). The function returns the formatted string, which is then assigned to the description variable. This provides a concise way to create a string representation of the object. This is a good example of how to improve code conciseness.

As a standalone function, run is useful for evaluating expressions or executing blocks of code that don’t require an object context. For example:

val result = run { val x = 10 val y = 20 x + y } println(result) // Output: 30 

Here, run is used to calculate the sum of two numbers and return the result. This can be useful for encapsulating complex calculations or operations. According to a study by JetBrains, the usage of scope functions like run correlates with higher code quality and fewer bugs. JetBrains Kotlin Blog offers further insights into Kotlin best practices. These best practices are important for professional development.

Differentiating ‘apply’ and ‘also’

Both apply and also are used to perform additional operations on an object, but they differ in what they return. The apply function returns the object itself after executing the block, while the also function also returns the object itself but makes it available as it inside the block. apply is commonly used for configuring objects, while also is used for performing side effects or additional actions without modifying the object’s primary state. Consider these two functions when you want to operate on an object without changing its core functionality.

apply is particularly useful when you want to configure an object’s properties immediately after creating it. For example:

data class Person(var name: String = "", var age: Int = 0) val person = Person().apply { name = "Bob" age = 25 } println(person) // Output: Person(name=Bob, age=25) 

In this example, apply allows you to set the name and age properties of the person object directly within the block. The function returns the modified person object, which is then assigned to the person variable. This provides a concise way to initialize and configure objects. This method is especially helpful when constructing complex objects.

also, on the other hand, is used for performing side effects or additional actions on an object without modifying its primary state. The context object is available as it inside the block. For example:

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

Here, also is used to print the contents of the numbers list before adding a new element. The function returns the original numbers list, which is then used to add the new element. This allows you to perform additional actions (like logging or debugging) without interfering with the main operation. This feature makes also a valuable tool for debugging and logging.

  • apply: Configure object properties and return the object.
  • also: Perform side effects and return the object.

Using ‘with’ Effectively

The with function in Kotlin is used to execute a block of code on an object, with the object available as this inside the block. Unlike the other scope functions, with is not an extension function; it’s a standalone function. The with function returns the result of the block. with is commonly used when you have an object and you want to perform multiple operations on it without repeatedly referring to it by name. It’s particularly useful for working with objects that don’t have a natural extension point.

Consider this example:

data class Configuration(var host: String = "", var port: Int = 8080) val config = Configuration() val result = with(config) { host = "localhost" port = 9000 "Configuration: host=$host, port=$port" } println(result) // Output: Configuration: host=localhost, port=9000 

In this case, with allows you to access the properties of the config object directly using this (which is implicit). The function returns the formatted string, which is then assigned to the result variable. This provides a concise way to configure and generate a string representation of the object. The with function is particularly useful for configuring UI elements in Android development. Using with can significantly reduce boilerplate code.

It’s important to note that with does not handle nullability. If the object you’re passing to with is null, you’ll get a NullPointerException. Therefore, it’s crucial to ensure that the object is not null before using with. Here’s another example:

val file = File("example.txt") with(file) { println("Name: ${name}") println("Path: ${absolutePath}") } 

This example demonstrates how with can be used to access the properties of a File object. This improves code readability and reduces redundancy. Learn more about Kotlin best practices.

  • with: Execute a block on an object as this, returning the result.
  • Ensure the object is not null before using with.

FAQ: Kotlin Scope Functions

When should I use `let`?
Use `let` when you want to execute a block of code on a non-null object, especially when transforming data or handling nullable values.
When should I use `run`?
Use `run` when you want to execute a block of code and return the result, either on an object (as `this`) or as **Question & Answer :** I wish to have a good example for each function `run`, `let`, `apply`, `also`, `with`

I have read medium.com : The difference between Kotlin’s functions: β€˜let’, β€˜apply’, β€˜with’, β€˜run’ and β€˜also’ but still lack of an example

All these functions are used for switching the scope of the current function / the variable. They are used to keep things that belong together in one place (mostly initializations).

Here are some examples:

run - returns anything you want and re-scopes the variable it’s used on to this

val password: Password = PasswordGenerator().run { seed = "someString" hash = {s -> someHash(s)} hashRepetitions = 1000 generate() } 

The password generator is now rescoped as this and we can therefore set seed, hash and hashRepetitions without using a variable. generate() will return an instance of Password.

apply is similar, but it will return this:

val generator = PasswordGenerator().apply { seed = "someString" hash = {s -> someHash(s)} hashRepetitions = 1000 } val pasword = generator.generate() 

That’s particularly useful as a replacement for the Builder pattern, and if you want to re-use certain configurations.

let - mostly used to avoid null checks, but can also be used as a replacement for run. The difference is, that this will still be the same as before and you access the re-scoped variable using it:

val fruitBasket = ... apple?.let { println("adding a ${it.color} apple!") fruitBasket.add(it) } 

The code above will add the apple to the basket only if it’s not null. Also notice that it is now not optional anymore so you won’t run into a NullPointerException here (aka. you don’t need to use ?. to access its attributes)

also - use it when you want to use apply, but don’t want to shadow this

class FruitBasket { private var weight = 0 fun addFrom(appleTree: AppleTree) { val apple = appleTree.pick().also { apple -> this.weight += apple.weight add(apple) } ... } ... fun add(fruit: Fruit) = ... } 

Using apply here would shadow this, so that this.weight would refer to the apple, and not to the fruit basket.


Note: I shamelessly took the examples from my blog Cargo Cult Programmer - Kotlin Basics: Standard Extension Functions