RizTech Academy logo
RizTech Academy
FunctionsLesson 5 of 530 min

Lambdas and higher-order functions

This is the lesson that unlocks idiomatic Kotlin. A lambda is a function with no name that you can pass around like a value, and a higher-order function is one that takes or returns a function. Together they are the machinery behind filter, map, coroutines, and most of what makes Kotlin expressive. It is worth going slowly here.

Functions as values

In Kotlin, a function is a value — you can store it in a variable, pass it as an argument, and return it. A lambda is the way to write a function inline, without giving it a name:

val square = { n: Int -> n * n }
println(square(5))            // 25

Read the lambda { n: Int -> n * n }: the part before -> is the parameters, the part after is the body, and the body's last expression is the return value. square now holds a function; calling square(5) runs it. Its type is (Int) -> Int — "takes an Int, returns an Int" — which is how you write a function type in Kotlin.

Higher-order functions: taking a function as a parameter

A higher-order function accepts a function as an argument. This is how you write code that does "something" without knowing in advance what the something is:

fun repeat(times: Int, action: (Int) -> Unit) {
    for (i in 0..<times) {
        action(i)
    }
}

repeat(3) { i -> println("Step $i") }
// Step 0
// Step 1
// Step 2

repeat takes an action of type (Int) -> Unit — a function that takes an Int and returns nothing — and calls it for each step. The caller supplies the what: here, printing. This is enormously powerful: repeat knows about looping; the lambda knows about the work; neither needs to know the other's details.

The trailing lambda convention — read this twice

Notice repeat(3) { i -> println("Step $i") }. The lambda is outside the parentheses. This is Kotlin's trailing lambda convention, and it is everywhere: when the last parameter of a function is a lambda, you can move it outside the parentheses. If the lambda is the only argument, you can drop the parentheses entirely:

listOf(1, 2, 3).forEach { println(it) }        // parentheses dropped — forEach takes only a lambda

This is why filter { }, map { }, and forEach { } look the way they do — they are ordinary higher-order functions, and the { } is a trailing lambda. Once you see that, a huge amount of Kotlin stops looking like magic syntax and starts looking like plain function calls.

it — the implicit single parameter

When a lambda has exactly one parameter, you can skip naming it and use the implicit name it:

val numbers = listOf(1, 2, 3, 4, 5, 6)
println(numbers.filter { it % 2 == 0 })     // [2, 4, 6]
println(numbers.map { it * 10 })            // [10, 20, 30, 40, 50, 60]

{ it % 2 == 0 } is shorthand for { n -> n % 2 == 0 }. it is the single argument — here, each number in turn. Use it for short, obvious lambdas; name the parameter when the lambda is longer or when it would be unclear (and always name it when lambdas are nested, or you cannot tell which it is which).

The collection operations, demystified

Now the collection operations you have been using make complete sense — they are higher-order functions taking lambdas:

val prices = listOf(120, 340, 90, 560, 210)

val expensive = prices.filter { it > 200 }          // [340, 560, 210]  — keep matching
val withTax = prices.map { it + it / 10 }           // add 10% to each  — transform
val total = prices.sumOf { it }                     // 1320             — add up
val anyExpensive = prices.any { it > 500 }          // true             — is there one?
val allCheap = prices.all { it < 1000 }             // true             — are they all?
val firstBig = prices.first { it > 300 }            // 340              — the first match

Each takes a lambda describing the condition or transformation, and each returns a result — a new list, a number, a boolean. This is the collections module in miniature; the point here is that there is no special syntax involved, just higher-order functions and trailing lambdas.

Returning a function, and function references

A higher-order function can also return a function:

fun multiplier(factor: Int): (Int) -> Int = { n -> n * factor }

val triple = multiplier(3)
println(triple(10))          // 30

multiplier(3) returns a lambda that multiplies by 3. And instead of writing a lambda that just calls an existing function, you can pass the function directly with :: — a function reference:

fun isEven(n: Int) = n % 2 == 0
println(listOf(1, 2, 3, 4).filter(::isEven))   // [2, 4]  — ::isEven instead of { isEven(it) }

::isEven refers to the isEven function as a value. It is cleaner than { isEven(it) } when the lambda would do nothing but forward its argument.

Why this matters

Lambdas and higher-order functions are not an advanced flourish — they are the foundation of how Kotlin is written. Every collection operation, every coroutine builder, every let/apply/also from the null-safety module, and most modern Kotlin APIs are built on them. When you are comfortable reading list.filter { it.age > 18 }.map { it.name } as "two higher-order functions with trailing lambdas", you can read almost any Kotlin code. The collections module next puts this to work in depth.

Check your work

What a lambda is. A function written inline with no name — { params -> body } — that you can pass as a value.

How to read { n: Int -> n * n }. Parameters before ->, body after; the last expression is the return value.

What a higher-order function is. One that takes or returns a function.

The trailing lambda convention. When the last parameter is a lambda, write it outside the parentheses; if it is the only argument, drop the parentheses.

What it is. The implicit name for a lambda's single parameter — use it for short lambdas, name the parameter otherwise.

Why the collection operations look the way they do. They are higher-order functions taking trailing lambdas — no special syntax.

How to return a function. Declare the return type as a function type and return a lambda.

What ::name is. A function reference — the function as a value, cleaner than { name(it) }.

Why this matters. Lambdas and higher-order functions underlie collections, coroutines, the scope functions, and most modern Kotlin APIs.

Practice

  1. Store a lambda in a val and call it. Check its type in the IDE ((Int) -> Int or similar).
  2. Write repeat(times, action) as a higher-order function and call it with a trailing lambda.
  3. Use filter, map, any, all, and first on a list of prices, each with a lambda. Predict each result before running.
  4. Rewrite a lambda { n -> n * 2 } using it, then decide when naming the parameter would read better.
  5. Write multiplier(factor) that returns a function, and use it to make a triple and a double.
  6. Replace a { isEven(it) } lambda with the function reference ::isEven.
  7. Read people.filter { it.age >= 18 }.map { it.name } aloud as "two higher-order functions with trailing lambdas", and write your own two-step chain.

Official documentation

Next module — Collections: lists, maps and sets, and the operations you just met, in depth.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship