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
- Store a lambda in a
valand call it. Check its type in the IDE ((Int) -> Intor similar). - Write
repeat(times, action)as a higher-order function and call it with a trailing lambda. - Use
filter,map,any,all, andfirston a list of prices, each with a lambda. Predict each result before running. - Rewrite a lambda
{ n -> n * 2 }usingit, then decide when naming the parameter would read better. - Write
multiplier(factor)that returns a function, and use it to make atripleand adouble. - Replace a
{ isEven(it) }lambda with the function reference::isEven. - 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
- Kotlin — Higher-order functions and lambdas — Lambdas, function types,
it, and trailing lambdas. - Kotlin — Lambdas: it — The implicit single parameter.
- Kotlin — Function references — The
::namesyntax. - Kotlin — Collection operations — Where these are used most, covered next.
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