In the last lesson you passed small blocks of code to filter, map and sortedBy. Those blocks are lambdas: functions without a name, written right where they're needed. In Kotlin, functions are values like any other. You can store them in variables, pass them to other functions and return them as results.
A function that takes or returns another function is called a higher-order function. They let you separate what to do from how to repeat or organise it. filter knows how to walk through a list; you just tell it which items to keep.
In this lesson you'll write lambdas with and without names for their parameters, read function types like (Int) -> Boolean, write your own higher-order functions, use the trailing lambda syntax that makes Kotlin code so readable, and reference existing functions with ::.
Lambda syntax and function types
val double: (Int) -> Int = { n -> n * 2 }
val add = { a: Int, b: Int -> a + b }
println(double(5)) // 10
println(add(2, 3)) // 5- A lambda is written in curly braces: parameters, then
->, then the body. - The last expression in the body is the result; there's no
return. - A function type describes inputs and output:
(Int) -> Inttakes an Int and returns one;(String, Int) -> Booleantakes two values;() -> Unittakes nothing and returns nothing. - If the type is known, parameter types can be left out:
val double: (Int) -> Int = { n -> n * 2 }.
it and trailing lambdas
When a lambda has exactly one parameter, you can skip naming it and use it:
val isEven: (Int) -> Boolean = { it % 2 == 0 }If a function's last parameter is a function, the lambda can go outside the brackets. If it's the only argument, the brackets can disappear entirely:
numbers.filter({ it > 0 }) // allowed, but unusual
numbers.filter() { it > 0 } // trailing lambda
numbers.filter { it > 0 } // the usual styleThis is why so much Kotlin code reads like repeat(3) { ... } or list.forEach { ... }.
Writing higher-order functions
Declare a parameter with a function type, then call it like a normal function:
fun applyTwice(x: Int, f: (Int) -> Int): Int = f(f(x))
applyTwice(3) { it + 10 } // 23A function can also return a function, which is useful for building customised behaviour:
fun multiplier(factor: Int): (Int) -> Int = { it * factor }
val triple = multiplier(3)
triple(7) // 21The returned lambda remembers factor even after multiplier has finished. That's called a closure.
Function references and returning
If an existing function already does the job, pass a reference to it with :: instead of wrapping it in a lambda:
fun isLong(word: String) = word.length > 5
words.filter(::isLong)
words.map(String::uppercase)Inside a lambda, a plain return would return from the enclosing function, which is rarely what you want. To skip to the next item in forEach, use a labelled return: return@forEach. Usually the clearer fix is to use filter first, so there's nothing to skip.
Closures that change captured variables
A lambda doesn't only read the variables around it; it can change them too. Each call to makeCounter below creates a fresh count, and the returned lambda keeps hold of it:
fun makeCounter(): () -> Int {
var count = 0
return { ++count }
}
val next = makeCounter()
println(next()) // 1
println(next()) // 2
val other = makeCounter()
println(other()) // 1, a separate countThis is how lambdas can carry private state without a class. Use it sparingly, though: when several lambdas change the same variable, it gets hard to follow who changed what.
Unpacking parameters, optional callbacks and lambdas in maps
When a lambda receives a pair or a map entry, you can unpack it in brackets, and replace any part you don't need with _:
prices.forEach { (item, price) -> println("$item: $price") }
prices.forEach { (_, price) -> println(price) }Function types can be nullable, which is a neat way to make a callback optional. Wrap the type in brackets before the ?, and call it with ?.invoke:
fun download(url: String, onDone: ((String) -> Unit)? = null) {
onDone?.invoke("done: $url")
}
download("a.txt") // no callback, nothing printed
download("b.txt") { println(it) } // prints done: b.txtBecause lambdas are values, you can also store them in collections. A map from symbols to operations makes a tiny calculator with no when at all:
val ops: Map<String, (Int, Int) -> Int> = mapOf(
"+" to { a, b -> a + b },
"*" to { a, b -> a * b },
)
println(ops["*"]?.invoke(6, 7)) // 42The lookup returns a nullable function, since the key might be missing, so the safe call ?.invoke is needed here as well.
Kotlin Lambdas Syntax
Lambda forms
val f: (Int) -> Int = { x -> x * 2 }
val g = { a: Int, b: Int -> a + b }
val h: (String) -> Int = { it.length }
fun run(action: () -> Unit) = action()
run { println("hi") }
fun maker(n: Int): (Int) -> Int = { it + n }
list.filter(::isValid)
list.forEach { if (it < 0) return@forEach; println(it) }Kotlin Lambdas Summary
- A lambda is a function value in braces:
{ a, b -> a + b }; its last expression is the result. - Function types like
(Int) -> Booleandescribe parameters and return types. itnames a single parameter; a trailing lambda goes outside the brackets.- Higher-order functions take or return functions; returned lambdas capture variables as closures.
::namereferences an existing function; usereturn@labelinside lambdas.
Kotlin Lambdas Examples
Each example shows the code, the output it produces and what each part does.
Lambdas as values
fun main() {
val square: (Int) -> Int = { it * it }
val greet = { name: String -> "Hello, $name!" }
val isAdult: (Int) -> Boolean = { age -> age >= 18 }
println(square(6))
println(greet("Priya"))
println(isAdult(16))
val operations = mapOf<String, (Int, Int) -> Int>(
"add" to { a, b -> a + b },
"multiply" to { a, b -> a * b }
)
println(operations["multiply"]?.invoke(4, 5))
}36
Hello, Priya!
false
20Line-by-line explanation
- Line 2With a single parameter, it refers to the value.
- Line 3The parameter type is written inside the lambda, so the variable's type is inferred.
- Lines 10 to 13Functions stored in a map, looked up by name.
- Line 14The map returns a nullable function, so it's called safely with ?.invoke.
Your own higher-order function
fun retry(times: Int, action: (Int) -> Boolean): Boolean {
for (attempt in 1..times) {
if (action(attempt)) {
println("Succeeded on attempt $attempt")
return true
}
println("Attempt $attempt failed")
}
return false
}
fun main() {
val ok = retry(3) { attempt -> attempt == 2 }
println("Result: $ok")
}Attempt 1 failed
Succeeded on attempt 2
Result: trueLine-by-line explanation
- Line 1action is a function that receives the attempt number and reports success.
- Line 3Calling the parameter runs the caller's lambda.
- Line 13A trailing lambda supplies the behaviour; here it pretends the second attempt works.
Closures and function references
fun counter(): () -> Int {
var count = 0
return { ++count }
}
fun isLong(word: String) = word.length > 5
fun main() {
val next = counter()
println(next())
println(next())
println(next())
val words = listOf("kotlin", "map", "lambda", "it")
println(words.filter(::isLong))
println(words.map(String::length))
}1
2
3
[kotlin, lambda]
[6, 3, 6, 2]Line-by-line explanation
- Lines 2 to 3The lambda captures count, so it keeps its value between calls.
- Lines 9 to 12Each call to next increases the same captured variable.
- Line 15A reference to isLong is passed instead of writing { isLong(it) }.
- Line 16String::length refers to a property of each string.
Common Kotlin Lambdas Mistakes
Writing return inside a lambda
fun printPositives(xs: List<Int>) {
xs.forEach {
if (it < 0) return // leaves printPositives completely
println(it)
}
}fun printPositives(xs: List<Int>) {
xs.filter { it >= 0 }.forEach { println(it) }
}Using it with two parameters
val add: (Int, Int) -> Int = { it + it } // error: it is only for one parameterval add: (Int, Int) -> Int = { a, b -> a + b }Forgetting to call the function
fun run(action: () -> Unit) {
action // does nothing
}fun run(action: () -> Unit) {
action()
}Kotlin Lambdas in Real Projects
Lambdas in Kotlin code
Lambdas are everywhere in Kotlin: click listeners in Android (button.setOnClickListener { ... }), every Jetpack Compose screen, collection pipelines, coroutine builders like launch { } and configuration DSLs such as Gradle's Kotlin build scripts. Learning to read trailing lambdas fluently is one of the most useful Kotlin skills you can build.
Kotlin Lambdas FAQ
What is the difference between a lambda and a function in Kotlin?
A function declared with fun has a name and lives in a file or class. A lambda is an unnamed piece of code written in braces that you can store in a variable or pass as an argument. You can turn a named function into a value with a reference such as ::isLong, so the two often work interchangeably.
What does it mean in a Kotlin lambda?
it is the implicit name of the single parameter when a lambda has exactly one and you haven't named it. In list.filter { it > 0 }, it is each item in turn. With two or more parameters you must name them, as in { a, b -> a + b }.
Why can't I use return inside a Kotlin lambda?
A plain return inside a lambda passed to an inline function such as forEach returns from the whole enclosing function, not just the lambda, and in other lambdas it isn't allowed at all. Use a labelled return like return@forEach to finish only the lambda, or let the last expression be the result.
What is a higher-order function?
A function that takes another function as a parameter or returns one. filter, map and repeat are all higher-order functions from the standard library. You write your own by giving a parameter a function type, such as f: (Int) -> Int, and calling it with f(x).
How do I pass a function as a parameter in Kotlin?
Declare the parameter with a function type, such as action: () -> Unit or check: (String) -> Boolean. Callers can pass a lambda, usually as a trailing lambda after the brackets, or a reference to an existing function with ::, as in words.filter(::isLong).
Kotlin Lambdas Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the lambda so it returns its number plus one.
val inc: (Int) -> Int = { + 1 }
What does this print?
val f = { x: Int -> x * 3 }
println(f(4))
What does this print?
fun applyTwice(x: Int, f: (Int) -> Int) = f(f(x))
println(applyTwice(2) { it * 5 })
What does the type (String) -> Boolean describe?
What does this print?
fun adder(n: Int): (Int) -> Int = { it + n }
val add5 = adder(5)
val add10 = adder(10)
println(add5(1) + add10(1))
Kotlin Lambdas Quiz
5 questions to check what you learned.
What is a lambda?
- A loop
- A type of list
- A class
- A function without a name, written as a value in braces
What is a higher-order function?
- A very fast function
- A function inside a class
- A function with many parameters
- A function that takes or returns another function
What does
list.filter(::isValid)pass to filter?- The result of isValid
- A new copy of isValid
- A reference to the isValid function
- Nothing
When can a lambda be written outside the brackets?
- When it's the last parameter of the function being called
- Always
- Only for forEach
- Never
What is a closure?
- A lambda that closes a file
- A lambda that captures and remembers variables from where it was created
- A function with no return value
- A finished coroutine