---
title: 'Lambdas and Higher-Order Functions'
source: 'https://academia.sh/en/courses/kotlin/lambdas-and-higher-order-functions'
course: 'The Kotlin Language'
language: en
updated: '2026-10-21T05:02:40+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Uses a lambda expression, the `it` name, and the trailing lambda syntax'
    - 'Writes a function type and a higher-order function that takes or returns a function'
    - 'Measures that two lambdas fall under the identity criterion, while a lambda that captures nothing is a single object'
---

# Lambdas and Higher-Order Functions

Introduces lambda syntax, function types, and functions that take functions; measures that two lambda expressions are not equal, while a lambda that captures nothing is a single object.

In the previous lesson we saw that a function reference such as `::plain` gives a separate but equal object each time it is taken. Now we write the function in place. A lambda is a function value without a name; it can be assigned to a variable, given to a function as an argument, or returned from a function. The question of this lesson continues the question of the previous one: are two lambdas with the same body equal?

## Lambda and Function Type

A lambda is written inside curly braces: first the parameters, then `->`, then the body. In a lambda with a single parameter whose type is known from the context, the parameter is not declared; the implicit name `it` is used in its place. A function type has the form `(Int) -> Int`: the parameter types are inside parentheses, and the return type follows the arrow.

A function that takes a function or returns a function is called a higher-order function. When a function whose last parameter has a function type is called, the lambda can be written outside the parentheses: this is called a trailing lambda. The `inline` keyword appears in this lesson only as a definition: the body of a higher-order function marked `inline`, and the lambda given to it, are placed at the call site at compile time. `inline` was not measured in this lesson.

In this lesson too, the measurements are made with the helper file introduced in the first lesson. The `Criterion` class takes a value, its twin, and a different value, and asks three questions; the file is given below exactly as it was.

```kotlin
// Criterion.kt — classifies the criterion that makes two values equal
class Criterion(val label: String, first: Any?, twin: Any?, other: Any?) {
    val sameObject = first === twin
    val equalTwin = first == twin
    val equalOther = first == other

    val name: String = when {
        sameObject -> "singleton"
        !equalTwin -> "identity"
        !equalOther -> "content"
        else -> "partial-content"
    }

    fun report() {
        println("$label: same-object=$sameObject, twin=$equalTwin, other=$equalOther -> $name")
    }
}

fun summarize(checks: List<Criterion>) {
    for (name in listOf("singleton", "identity", "content", "partial-content")) {
        println("$name: ${checks.count { it.name == name }}")
    }
}
```

A function type is an ordinary type: it can be written as the type of a variable, a parameter, or a return value. In the line `val increment: (Int) -> Int = { it + 1 }` we wrote the type explicitly, so there is no need to declare a parameter in the lambda, and `it` can be used. When the type is not written, the parameter is declared inside the lambda in the form `n: Int ->`; the compiler has to learn the parameter type from one of the two ways.

The program below shows these together. `applyTwice` applies an operation two times, and `makeAdder` returns a function.

```kotlin
// Main.kt — lambda, it, trailing lambda, and function type
fun applyTwice(value: Int, operation: (Int) -> Int): Int = operation(operation(value))

fun makeAdder(amount: Int): (Int) -> Int = { it + amount }

fun main() {
    val twice = { n: Int -> n * 2 }
    val increment: (Int) -> Int = { it + 1 }
    println(twice(4))
    println(applyTwice(3, increment))
    println(applyTwice(3) { it * 10 })
    val addFive = makeAdder(5)
    println(addFive(10))
    println(listOf(1, 2, 3).map { it * it })
}
```

```
8
5
300
15
[1, 4, 9]
```

`twice(4)` called a lambda directly and gave `8`. `applyTwice(3, increment)` added one to three twice, and `5` came out. `applyTwice(3) { it * 10 }` is the trailing lambda syntax: three, thirty, three hundred; the result is `300`. `makeAdder(5)` returned a function that adds five, and this function gave `15` on `10`. In the last line `map` applied the lambda to each element, and `[1, 4, 9]` came out.

**LB45.** `it` was shown only in lambdas with a single parameter; the naming of parameters in a lambda with several parameters stayed limited to the single parameter in the `twice` example.

**LB46.** Higher-order functions were shown only on `Int` and with `map`; the other collection functions such as `filter` and `fold` are the work of a later topic.

## Are Two Lambdas Equal

The measurement is built from four observations. `two-lambdas`: the expression `{ 1 }` written in two separate places. `same-call-site`: the `makeConstant` function, which returns the lambda `{ 1 }` written in one place, called twice. `capturing-lambda`: a lambda that uses an outer variable, that is, the `amount` parameter, with `makeAdderFor(1)` called twice. `function-reference`: `::inc` twice, one more repetition of the observation of the previous lesson.

Capture is when a lambda uses a variable outside its own body. The lambda inside `makeAdderFor` captures the value of `amount`; `{ 1 }`, on the other hand, captures nothing from outside.

```kotlin
// Main.kt — lambdas and identity
fun makeConstant(): () -> Int = { 1 }

fun makeAdderFor(amount: Int): (Int) -> Int = { it + amount }

fun inc(n: Int): Int = n + 1

fun dec(n: Int): Int = n - 1

fun main() {
    val checks = listOf(
        Criterion("two-lambdas", { 1 }, { 1 }, { 2 }),
        Criterion("same-call-site", makeConstant(), makeConstant(), { 2 }),
        Criterion("capturing-lambda", makeAdderFor(1), makeAdderFor(1), makeAdderFor(2)),
        Criterion("function-reference", ::inc, ::inc, ::dec),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
two-lambdas: same-object=false, twin=false, other=false -> identity
same-call-site: same-object=true, twin=true, other=false -> singleton
capturing-lambda: same-object=false, twin=false, other=false -> identity
function-reference: same-object=false, twin=true, other=false -> content
singleton: 1
identity: 2
content: 1
partial-content: 0
```

`two-lambdas` is identity: two lambdas with the same body that are written separately are not equal, `twin=false`. `capturing-lambda` is also identity: when `makeAdderFor(1)` was called twice, two separate lambda objects were born, and even though they captured the same value, they are not equal.

`same-call-site` is a singleton: `makeConstant()` returned the same object in two calls, `same-object=true`. This lambda captures nothing from outside; in the sandbox it was observed as the same instance at every call. `function-reference` is content, and the result of the previous lesson was verified again here. The summary lines give one singleton, two identity, one content, and zero partial content.

**LB47.** That a lambda that captures nothing is a single object was measured on the JVM target of Kotlin 2.4.21; this is not a language contract but a result observed in that version.

**LB48.** The `capturing-lambda` observation was made with the capture of a single `Int`; it is not claimed that the result stays the same when the type or the number of captured variables changes.

## The Captured Variable Is Separate for Each Creation

A lambda can read and change a `var` variable outside itself. The question is how many copies of this variable exist. The block below writes a function that produces a counter, sets up two separate counters, and gives the same counter two separate names.

```kotlin
// Main.kt — a captured var is separate for each creation
fun makeCounter(): () -> Int {
    var count = 0
    return { ++count }
}

fun main() {
    val first = makeCounter()
    val second = makeCounter()
    println(first())
    println(first())
    println(second())
    println(first())
    val shared = makeCounter()
    val alias = shared
    println(shared())
    println(alias())
}
```

```
1
2
1
3
1
2
```

`first` was called twice and gave `1` and `2`; `second` gave `1` at its first call. The two counters captured separate `count` variables, because a new `count` is created each time `makeCounter` is called. When `first` was called again, it printed `3`, and the call of `second` did not affect it. The last two lines gave `1` and `2`: since `alias` points to the same lambda object as `shared`, both incremented the same counter.

The rule is this: the captured variable lives together with the lambda object, and there is one copy per object. The frequent mistake is to assume that the counter is reset each time the function is called, or to assume that all lambdas share the same variable. In Java, a lambda can capture only variables that are effectively final, while Kotlin allows capturing a `var`; behind this, the variable is known to be wrapped in a reference box, but this was not measured in this lesson.

**LB49.** The counter measurement was made on a single thread; what happens when several threads increment the same captured variable was not measured in this lesson.

## Returning from a Lambda

Writing `return` inside a lambda sometimes ends the function and sometimes gives an error. The block below tries three cases: an unlabeled `return` inside `forEach`, `return@forEach` inside `forEach`, and `return@label` with its own label inside `map`.

```kotlin
// Main.kt — returning from a lambda
fun findFirstEven(values: List<Int>): Int {
    values.forEach {
        if (it % 2 == 0) return it
    }
    return -1
}

fun main() {
    println(findFirstEven(listOf(1, 3, 4, 6)))
    println(findFirstEven(listOf(1, 3)))
    val visited = mutableListOf<Int>()
    listOf(1, 2, 3, 4).forEach {
        if (it == 2) return@forEach
        visited.add(it)
    }
    println(visited)
    val labeled = listOf(1, 2, 3).map label@{
        if (it == 2) return@label 20
        it
    }
    println(labeled)
}
```

```
4
-1
[1, 3, 4]
[1, 20, 3]
```

`findFirstEven(listOf(1, 3, 4, 6))` gave `4`: `forEach` is an inline function, so the `return` inside the lambda ends the outer function, and 4, the first even number, is returned. In the second call there was no even number, so the loop ended and `-1` was returned.

The third line gave `[1, 3, 4]`. `return@forEach` ends only that run of the lambda, that is, it behaves like `continue`: 2 was skipped, and the rest were added. The fourth line gave `[1, 20, 3]`; `return@label` sets the value of the lambda, that is, inside `map` the value 2 was replaced with 20.

The frequent mistake is to expect an unlabeled `return` inside `forEach` to skip only one element: it ends the whole function. This behavior is possible because `forEach` is inline; in a lambda that is not inline, an unlabeled `return` gives a compile error, but this branch was not measured. In Java, a `return` inside a lambda exits only the lambda and does not end the outer method.

**LB50.** The return behavior was measured with `forEach` and `map`; the error message given when an unlabeled `return` is written in a higher-order function that is not inline was not measured in this lesson.

## Lambda with Receiver and SAM Conversion

Some lambdas reach the members of a receiver object directly. The block below sets up a lambda of type `StringBuilder.() -> Unit`, writes lambdas for the Java interfaces `Runnable` and `Comparator`, and produces a lambda that combines two functions.

```kotlin
// Main.kt — lambda with receiver and SAM conversion
fun main() {
    val build: StringBuilder.() -> Unit = {
        append("kot")
        append("lin")
    }
    println(StringBuilder().apply(build).toString())
    val task = Runnable { println("ran") }
    task.run()
    val byLength = Comparator<String> { a, b -> a.length - b.length }
    println(listOf("ccc", "a", "bb").sortedWith(byLength))
    val compose: ((Int) -> Int, (Int) -> Int) -> (Int) -> Int = { f, g -> { x -> g(f(x)) } }
    val doubleThenInc = compose({ it * 2 }, { it + 1 })
    println(doubleThenInc(5))
}
```

```
kotlin
ran
[a, bb, ccc]
11
```

The first line gave `kotlin`. The `build` variable is a lambda with a receiver: `append` is called directly in its body, because a `StringBuilder` is given as `this`. `apply(build)` ran it on a new `StringBuilder`. The second line is `ran`: the `Runnable { ... }` syntax creates a Java interface that has a single abstract method with a lambda. This conversion is called SAM conversion (single abstract method). The third line gave `[a, bb, ccc]`: `Comparator` was also written with a lambda in the same way and sorted by length.

The fourth line gave `11`. `compose` takes two functions and returns a third; `doubleThenInc(5)` first multiplies 5 by two (10), then adds one (11). The order matters: since it is written `g(f(x))`, `f` runs first. The frequent mistake is to assume the reverse order in a composition.

**LB51.** SAM conversion was measured with Java interfaces; that an interface written in Kotlin needs a separate `fun interface` declaration was not measured in this lesson.

## Habits That Come from the Measurements

Three questions should be asked when working with lambdas: what is captured, where does the code return from, and which type is expected. On capture, the measurement gave a clear rule: the captured variable lives together with the lambda object, and a separate copy is created at each creation. This makes it possible to build small objects that carry state, such as a counter or a cache, without writing a class; but because where the state is stored is not visible in the code, it is a hidden dependency for the reader. If the state needs to be long-lived or shared, an explicit class is more readable.

On returning, the most important distinction is whether the lambda is inline. An unlabeled `return` inside `forEach` ends the outer function; in the measurement `findFirstEven` found the first even number and returned. This behavior is useful, because it expresses an early exit without writing a loop. But if it is used wrongly where `continue` is expected, it quietly ends the whole function. To skip, `return@forEach` is written; to set the value, `return@label` is written. For readability, lambdas with more than one return point should be split into small named functions.

On types, a lambda with a receiver (`StringBuilder.() -> Unit`) works with `this` in the body and reaches the members of the object directly. This is the basis for writing builder code, and it is the mechanism of scope functions in the next lesson. SAM conversion makes it possible to build Java interfaces with a lambda; for interfaces written in Kotlin, `fun interface` is needed, and this was not measured.

The `compose` example, which combines functions, shows that functions are values. Since the order is `g(f(x))`, `f` runs first, and for the input 5 the result was 11. The reverse order would give a different result, but that calculation was not measured; only the output 11 came from the sandbox. For this reason, testing the order of a composition with a small example is enough. In function composition libraries this order is often confused, so it needs to be verified with a small example.

Comparison with Java: a Java lambda captures only variables that are not reassigned, and `return` exits only the lambda. Kotlin offers a wider behavior in both; the cost of the flexibility is that the reader must know which `return` ends what. When reviewing code, looking first at whether the enclosing function is inline and then at whether there is a label, for each `return`, removes this ambiguity quickly.

## The Bounding Measurement

The identity criterion for lambdas is broken by two limits. The first is capture: a lambda that captures nothing becomes a single object at the call site and comes out as a singleton, while a lambda that captures becomes a new object at each call and comes out as identity. That is, whether two lambdas are equal is determined not by their bodies but by the place where they are written and whether they capture something. The second is the function reference: when `::inc` is taken twice, the objects are separate but equal.

## Summary

- A lambda is written in the form `{ parameters -> body }`; a single parameter can take the name `it`, and if the last parameter has a function type, the lambda can be written outside the parentheses.
- A function type has the form `(Int) -> Int`; a function that takes or returns a function is called a higher-order function.
- Two lambdas with the same body are not equal if they are written separately: identity. A capturing lambda is a new object at every call.
- A lambda that captures nothing gave the same object every time from the same call site: singleton. `::inc` taken twice is content.
- This lesson's criterion: **identity**; bounding measurement: a lambda that captures nothing is a singleton, and the `::inc` reference is content.

## Next Step

We have seen how to carry lambdas as values. In the last lesson we examine five ready-made higher-order functions that run a lambda in the context of an object: scope functions. `let`, `run`, `with`, `apply`, and `also` work on the same object, but one gives the object as `this`, another as `it`; we also measure which of them returns what.
