This episode masters the functional side of Kotlin: lambdas and anonymous functions, function references, scope functions let, run, apply, also, and with, immutability patterns, and the functional idioms that make code more declarative and easier to test.

Kotlin is a language that combines OOP and functional programming. Episode 7 focuses on its functional side: lambdas, anonymous functions, function references, and the scope functions that are a hallmark of modern Kotlin code.
Scope functions like let, run, apply, also, and with may look magical at first, but they are really just ordinary functions with lambdas that take a receiver. Understanding them means understanding half of the Kotlin idioms you'll see in production.
After this episode, you'll write data transformations in a declarative style and use scope functions deliberately — not just copy them from Stack Overflow.
A lambda is a function block that can be stored in a variable or passed as an argument. Its general form: parameters inside parentheses, then the body after an arrow:
val tambah: (Int, Int) -> Int = { a, b -> a + b }
val sapa = { nama: String -> "Halo, $nama" }
println(tambah(3, 4))
println(sapa("Budi"))A lambda's type is written as (Int, Int) -> Int — parameters inside parentheses, the result type after the arrow. Kotlin also allows trailing lambdas: if the lambda is the last argument of a function, the parentheses can be omitted, as you already saw with filter { ... } in episode 5.
An anonymous function is similar to a lambda but uses the fun keyword and allows explicit return statements:
val angka = listOf(1, 2, 3, 4)
fun genap(n: Int): Boolean = n % 2 == 0
println(angka.filter(::genap))
println(angka.filter { it % 2 == 0 })::genap is a function reference — it turns a named function into a function value. Both lines produce the same result; the first uses a reference, the second uses a lambda. Function references are useful for existing functions you want to reuse.
let uses the value as a lambda argument (it) and returns the lambda's result — perfect for processing nullable values:
val teks: String? = "hello"
val panjang = teks?.let { it.length } ?: 0
println(panjang) // 5run is similar to let but uses a receiver (this) instead of a parameter, and also returns the lambda's result. The pattern teks?.let { it.length } only executes the lambda if teks is not null — a very common combination in production code.
apply returns the receiver itself after running the lambda with this as the receiver — perfect for configuring objects. also also returns the receiver, but the lambda receives it, which suits logging:
class Pesanan {
var id: String = ""
var total: Int = 0
}
val p = Pesanan().apply {
id = "ORD-001"
total = 150000
}.also {
println("Pesanan dibuat: ${it.id}")
}apply lets you initialize an object's properties without repeating the object name, and also lets you slip in side effects like logging without changing the flow. Both return the same p.
with uses a receiver and returns the lambda's result — useful when calling many methods on the same object:
val hasil = with(StringBuilder()) {
append("A")
append("B")
toString()
}
println(hasil) // ABA quick selection guide: use let for transforming nullable values, apply for object initialization, also for side effects, and run and with for computation within a receiver context. Don't overdo it — if a lambda makes the code hard to read, use a plain statement instead.
Kotlin encourages immutability: val, read-only collections, and data class with copy. Immutable data is safe to share across threads, easier to reason about, and forms the foundation of functional programming:
data class Item(val nama: String, val harga: Int)
val items = listOf(
Item("Kopi", 15000),
Item("Teh", 8000),
Item("Susu", 12000),
)
val total = items
.filter { it.harga > 10000 }
.map { it.harga }
.sum()The filter and map chain does not modify items — both produce new collections. This pattern lets data flow from transformation to transformation without side effects, and the results are easy to test because the functions are pure.
Some idioms commonly used in functional Kotlin code:
map and filter for transforming and filtering without manual loops.fold and reduce for accumulating values.data class with copy for updating immutable data.Functional idioms are most powerful when combined with the collection API from episode 5. For example, computing the average price of certain items becomes a single expression. Repeated practice will make these patterns surface automatically as you write code.
Episode 7 opened up the functional side of Kotlin: lambdas with trailing lambda syntax, anonymous functions and function references, all five scope functions with their individual uses, and the immutability habits that make code safer and easier to test.
The key takeaways:
(Params) -> Hasil.::namaFungsi creates a function reference for reusing functions.let and run return a result; apply and also return the receiver.val, read-only collections, and copy for immutable data.In episode 8 we'll discuss null-safe data handling — data classes and destructuring declarations, sealed classes for state, the Result type with idiomatic error handling, and serialization with kotlinx.serialization or Jackson.