← Back to Kotlin Course | Chapter 5: Null Safety | Lesson 5 of 6

let, run, also, apply, with

These five words are Kotlin's toolbox of little helpers for doing something with a value and deciding what you get back afterward.
Syntax
kotlin
val r1 = receiver.let { /* it -> */ result }
val r2 = receiver.run { /* this -> */ result }
val r3 = receiver.also { /* it -> */ }     // returns receiver
val r4 = receiver.apply { /* this -> */ }   // returns receiver
val r5 = with(receiver) { /* this -> */ result }

let for Null Checks and Transformations

let executes a block with the receiver available as it and returns whatever the block produces, and is commonly combined with ?. to run code only when a nullable value is present.

Example: let for Null Checks and Transformations

markup
// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `name` (a `String?`), set to `"Kotlin"`
    val name: String? = "Kotlin"
    // Call `name?.let` with a trailing lambda, storing the result in `length`
    val length = name?.let {
        // Print "Name is not null: $it" to the console, with a trailing newline
        println("Name is not null: $it")
        it.length
    }
    // Print "Length: $length" to the console, with a trailing newline
    println("Length: $length")
}

run for Grouped Computation

run executes a block with the receiver available as this, returning the block's result -- useful for computing a value from several steps involving that receiver's members directly.

Example: run for Grouped Computation

markup
// Define a class `Config`
class Config(var name: String = "", var port: Int = 8080)

// Entry point: execution of the program starts here
fun main() {
    // Call `Config().run` with a trailing lambda, storing the result in `summary`
    val summary = Config().run {
        name = "Server"
        port = 9090
        "$name running on port $port"
    }
    // Print `summary` to the console, with a trailing newline
    println(summary)
}

also for Side Effects

also runs a block with the receiver as it but always returns the original receiver unchanged, which makes it ideal for logging or debugging in the middle of a chain of calls.

Example: also for Side Effects

markup
// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `numbers`, set to `mutableListOf(1, 2, 3)`
    val numbers = mutableListOf(1, 2, 3)
        .also { println("Original list: $it") }
    // Add `4` to `numbers`
    numbers.add(4)
    // Print "After adding: $numbers" to the console, with a trailing newline
    println("After adding: $numbers")
}

apply for Configuring an Object

apply runs a block with the receiver as this and returns that same receiver, making it perfect for setting up several properties on a newly created object in one expression.

Example: apply for Configuring an Object

markup
// Define a class `Person`
class Person {
    // Declare a mutable variable `name` (a String), initialized to `""`
    var name: String = ""
    // Declare a mutable variable `age` (an Int), initialized to `0`
    var age: Int = 0
}

// Entry point: execution of the program starts here
fun main() {
    // Call `Person().apply` with a trailing lambda, storing the result in `person`
    val person = Person().apply {
        name = "Ana"
        age = 30
    }
    // Print "${person.name}, ${person.age}" to the console, with a trailing newline
    println("${person.name}, ${person.age}")
}

with for Operating on an Existing Object

with(receiver) { ... } is not an extension function but a regular function that takes an object and a lambda where that object is available as this, returning the lambda's result -- useful when you already have the object in hand.

Example: with for Operating on an Existing Object

markup
// Define a class `Rectangle`
class Rectangle(val width: Int, val height: Int)

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `rect`, set to `Rectangle(4, 5)`
    val rect = Rectangle(4, 5)
    // Call `with(rect)` with a trailing lambda, storing the result in `area`
    val area = with(rect) {
        width * height
    }
    // Print "Area: $area" to the console, with a trailing newline
    println("Area: $area")
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Mixing up apply and also; apply returns the receiver itself (this) while also also returns the receiver but exposes it as it.
  2. Mixing up let and run; both use this/it differently and return the lambda's result, easy to misremember which uses which.
  3. Overusing scope functions to chain many unrelated operations together, making the code harder to read than plain statements would be.
Chapter Summary
  • let runs a block with the value as it and returns the block's result -- often used with ?. for null checks.
  • run runs a block with the value as this and returns the block's result.
  • also runs a block with the value as it but returns the original value -- good for side effects like logging.
  • apply runs a block with the value as this and returns the original value -- good for configuring an object.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.