Lambdas
A lambda is an unnamed function that can be stored in a variable, passed to another function, or returned as a result.
val double: (Int) -> Int = { value -> value * 2 }
println(double(4)) // 8
(Int) -> Int is the function type: it receives one Int and returns one Int.
Read it from left to right:
(Int)— the lambda accepts oneInt.->— separates the inputs from the result.- The final
Int— the lambda returns anInt. { value -> value * 2 }—valueis the input; the final expression is returned.
8, followed by -6.Reading lambda syntax
val label: (Int, String) -> String = { id, name ->
"$id: $name"
}
- Parameters appear before
->. - The body appears after
->. - The final expression becomes the returned value.
- The expected function type helps Kotlin infer parameter types.
The implicit it parameter
For a one-parameter lambda, Kotlin can name the parameter it automatically:
val lengths = listOf("Kotlin", "Java").map { it.length }
Use it only when the meaning is obvious. In nested or longer lambdas, an explicit name is easier to read:
users.filter { user -> user.isActive }
Use it for a tiny, obvious lambda. Name the parameter when the lambda is nested, has several lines, or represents an important business object.
[Rakesh].Trailing lambda syntax
If the last argument is a function, the lambda can move outside the parentheses:
repeat(3) { index ->
println(index)
}
If it is the only argument, the parentheses can disappear. This syntax is common in Compose:
Button(onClick = { viewModel.save() }) {
Text("Save")
}
Capturing outside values
var total = 0
val add: (Int) -> Unit = { value -> total += value }
add(5)
println(total) // 5
The lambda captures total, meaning it keeps access to a value declared outside its body.
It captures access to the variable, not a frozen copy of its old value. Every invocation reads and updates the current total.
8.A lambda that outlives an Activity, Fragment, View, or large object can keep that object reachable longer than expected. Lifecycle ownership matters more than the lambda syntax itself.
Callable references
When an existing function already has the required shape, pass a reference instead of wrapping it:
fun normalize(value: String) = value.trim().lowercase()
val result = listOf(" A ", " B ").map(::normalize)
Member references are also possible:
val lengths = listOf("A", "Android").map(String::length)
::normalize is a reference to the existing function. In this example, it replaces { value -> normalize(value) } without changing the behavior.
[kotlin, android], followed by [1, 7].Lambdas with receivers
StringBuilder.() -> Unit means the lambda runs with a StringBuilder as its receiver, so its members can be called without a variable name.
fun buildMessage(block: StringBuilder.() -> Unit): String =
StringBuilder().apply(block).toString()
val message = buildMessage {
append("Hello")
append(" Android")
}
This style powers builders and Kotlin DSLs, including many Compose APIs. Use it when the receiver provides a clear, focused vocabulary.
Execution of buildMessage:
StringBuilder()creates one mutable builder.apply(block)runs the caller's lambda with that builder asthis.appendcalls update the same builder.toString()creates the final immutable string.
Result: 8.Corner cases
Nullable lambda versus lambda returning null
val optionalAction: (() -> Unit)? = null
val nullableResult: () -> String? = { null }
optionalActionmay contain no function at all.nullableResultalways contains a function, but calling it may return null.
action exists = false, followed by result = null.Android-only example
The Button { ... } snippet above requires the Compose compiler and Android/Compose libraries, so it cannot run in plain Kotlin Playground. The standalone lambda behavior is demonstrated by the surrounding Playground examples.
Common mistakes
1. Too many it values hide the meaning
✕ Hard to read
users.filter { it.orders.any { it.total > 1_000 } }
✓ Clear names
users.filter { user ->
user.orders.any { order -> order.total > 1_000 }
}
Both compile. The second version makes it obvious which object owns orders and which object has total.
2. Capturing a Fragment in long-running work
✕ Avoid
repository.loadUser { user ->
binding.name.text = user.name // May run after the Fragment view is destroyed
}
✓ Prefer lifecycle-owned collection
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state -> render(state) }
}
}
The problem is not “lambdas cause leaks.” The problem is that the callback may outlive the View it captured. Give long-running work a lifecycle owner.
3. Capturing mutable loop state
✕ Surprising result
var selectedId = 1L
val onClick = { println(selectedId) }
selectedId = 2L
onClick() // Prints 2, not 1
✓ Capture the intended value
val selectedId = 1L
val onClick = { println(selectedId) }
A lambda captures access to the variable, not necessarily a frozen copy of its old value.
A lambda is an anonymous function value. Its type describes its parameter and return types, such as (Int) -> String. Lambdas can capture outside values, use trailing-lambda syntax, and run with a receiver. Captures are convenient, but lifecycle and concurrency ownership must still be handled correctly.