Learn Kotlin - Functional Programming & Lambdas
Episode 7 of 23

Learn Kotlin - Functional Programming & Lambdas

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

Lambda and Anonymous Functions

Lambda Syntax

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:

KotlinLambda dasar
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.

Anonymous Functions and Function References

An anonymous function is similar to a lambda but uses the fun keyword and allows explicit return statements:

KotlinFunction reference
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.

Scope Functions

let and run: Transforming Values

let uses the value as a lambda argument (it) and returns the lambda's result — perfect for processing nullable values:

Kotlinlet untuk nilai nullable
val teks: String? = "hello"
val panjang = teks?.let { it.length } ?: 0
println(panjang) // 5

run 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 and also: Side Effects and Initialization

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:

Kotlinapply dan also
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 and Choosing the Right One

with uses a receiver and returns the lambda's result — useful when calling many methods on the same object:

Kotlinwith untuk operasi berulang
val hasil = with(StringBuilder()) {
    append("A")
    append("B")
    toString()
}
println(hasil) // AB

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

Immutability and Data Transformation

Immutable Data as the Default

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:

KotlinTransformasi immutable
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.

Functional Idioms in Kotlin

Writing in a Functional Style

Some idioms commonly used in functional Kotlin code:

  • map and filter for transforming and filtering without manual loops.
  • fold and reduce for accumulating values.
  • Scope functions for controlled initialization and side effects.
  • Function references for reusing named functions.
  • data class with copy for updating immutable data.

Combining with the Collection API

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.

Closing

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:

  • A lambda is stored as a value typed (Params) -> Hasil.
  • Trailing lambdas drop the call parentheses for the last argument.
  • ::namaFungsi creates a function reference for reusing functions.
  • let and run return a result; apply and also return the receiver.
  • Choose the scope function by intent: initialization, transformation, or side effects.
  • Prefer 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.

Learn Kotlin - Functional Programming & Lambdas | Learn Kotlin