← 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.

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
fun main() {
    val name: String? = "Kotlin"
    val length = name?.let {
        println("Name is not null: $it")
        it.length
    }
    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
class Config(var name: String = "", var port: Int = 8080)

fun main() {
    val summary = Config().run {
        name = "Server"
        port = 9090
        "$name running on port $port"
    }
    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
fun main() {
    val numbers = mutableListOf(1, 2, 3)
        .also { println("Original list: $it") }
    numbers.add(4)
    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
class Person {
    var name: String = ""
    var age: Int = 0
}

fun main() {
    val person = Person().apply {
        name = "Ana"
        age = 30
    }
    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
class Rectangle(val width: Int, val height: Int)

fun main() {
    val rect = Rectangle(4, 5)
    val area = with(rect) {
        width * height
    }
    println("Area: $area")
}
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.