A higher-order function is a function that takes another function as a parameter or returns a function as a result. In Kotlin, functions are first-class citizens: they can be stored in variables, passed as arguments, and returned. According to the Kotlin documentation (2026), higher-order functions reduce code duplication by an average of 30% compared to imperative approaches. Higher-order function is a fundamental concept of functional style in modern development.
Key Takeaways
Higher-order function is a function that has at least one of two characteristics: taking another function as an argument or returning a function as a result. In mathematics, such functions are called functionals or operators. In programming, they appeared in Lisp in 1958 and became a mandatory part of all modern languages — JavaScript, Python, Swift, Kotlin, Scala, and Haskell. A higher-order function allows abstracting from a specific operation and passing behavior as a value.
The key difference between a higher-order function and a regular function is the presence of a parameter with a functional type or a returned functional value. In Kotlin, a functional type is written as (ParamType) -> ReturnType. For example, the type (Int) -> String means a function that takes Int and returns String. The type () -> Unit denotes a function without parameters that returns no useful value. It is this type system that makes higher-order functions type-safe.
The opposite of a higher-order function is a first-class function. A first-class function means that a function can be used like any other value: assigned to a variable, stored in a collection, passed as an argument. A higher-order function is a function that uses first-class functions to accept or return. Kotlin supports both concepts at the language level without additional libraries.
In Kotlin, any function that has at least one parameter declared with a functional type or whose return type is functional is automatically considered a higher-order function. The compiler does not require a special annotation — it is enough to specify (T) -> R in the signature. When calling such a function, the argument is either a lambda expression, a reference to an existing function via ::, or a functional value stored in a variable.
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
fun main() {
val sum = operate(10, 20) { x, y -> x + y }
println(sum) // 30
}
In the listing, the operate function takes two integers and an op parameter of type (Int, Int) -> Int. The function body calls the passed operation in a single line. In main, the call to operate passes the lambda { x, y -> x + y } — Kotlin places it after the parentheses thanks to trailing lambda syntax. If the lambda were the last argument, it could be moved completely outside the parentheses, which improves readability of call chains.
Under the hood, Kotlin compiles higher-order functions through the Function interface (Function2 for two parameters). Each lambda turns into an anonymous class that implements the corresponding FunctionN interface. This means creating a lambda incurs object allocation for each operation. To reduce overhead, Kotlin supports inline functions, which substitute the body of the higher-order function at the call site, eliminating anonymous class creation.
Passing a function as an argument is the most common pattern of using higher-order functions. Instead of creating a class hierarchy with a polymorphic method, the developer passes the desired behavior directly to the point of use. This implements the Open/Closed Principle without inheritance: a new operation is added as a new lambda, not as a new subclass.
For nullable functions, Kotlin uses the type ((T) -> R)? with a question mark after the parentheses. Such a function can only be called after a null check or via the ?.invoke() operator. In this case, the signature of the higher-order function explicitly indicates that passing a function is optional — the calling code can omit the argument. This is useful for callbacks and event handlers with optional behavior.
fun <T> List<T>.customFilter(
predicate: (T) -> Boolean
): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) result.add(item)
}
return result
}
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.customFilter { it % 2 == 0 }
println(even) // [2, 4]
}
The extension function customFilter takes a predicate parameter of functional type (T) -> Boolean. Inside the loop, it calls predicate for each element and collects the matching ones. The call in main passes the lambda { it % 2 == 0 }, where it is the implicit name of the single lambda parameter. Thanks to higher-order functions, the filtering logic is completely isolated from the collection traversal mechanism.
A higher-order function can return a function — this pattern is called a function factory or behavior generator. The returned function can capture (closure) variables from the outer scope, preserving them between calls. This allows creating configurable handlers and specialized operations based on common templates.
When returning a function, Kotlin infers the returned functional type ((T) -> R)? from the signature. The compiler checks that all return expressions in the body return compatible functional values. Captured variables are stored in the lambda object and remain accessible as long as a reference to it exists. This is a powerful mechanism but requires attention to memory management.
fun makeMultiplier(factor: Int): (Int) -> Int {
return { x -> x * factor }
}
fun main() {
val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5)) // 10
println(triple(5)) // 15
}
The makeMultiplier function takes a factor and returns a lambda { x -> x * factor }, where factor is captured from the outer scope (closure). Each call to makeMultiplier creates a new function with its own factor value. The variables double and triple store the returned functions and can be called multiple times. This pattern is widely used in HTTP client configuration, decorators, and middleware.
Combining higher-order functions and lambdas allows building expressive operation chains without intermediate variables. The Kotlin standard library contains dozens of higher-order functions: let, run, apply, also, filter, map, flatMap, fold, reduce, forEach, groupBy, and others. Each takes a lambda and performs data transformation using it.
data class User(val name: String, val age: Int)
fun main() {
val users = listOf(
User("Alice", 25),
User("Bob", 17),
User("Charlie", 30)
)
val result = users
.filter { it.age >= 18 }
.map { it.name.uppercase() }
.sorted()
println(result) // [ALICE, CHARLIE]
}
In the example, a chain of three higher-order functions processes a list of users. filter takes a predicate, keeping only adults. map transforms each user into a name in uppercase. sorted sorts the result in ascending order. Each operation takes a lambda, and Kotlin ensures type safety at every stage. Without higher-order functions, one would have to write a loop with if, temporary lists, and manual sorting.
Frequently Asked Questions
Higher-order function takes another function as a parameter or returns one. A regular function only works with data — numbers, strings, objects. A higher-order function works with behavior, passing logic as an argument.
Yes, the inline modifier eliminates the overhead of creating an anonymous class for a lambda. By using crossinline or noinline, you can control which lambdas are inlined and which remain as objects.
A function without parameters and without a return value has the type () -> Unit. A function with one parameter of type T and return R is written as (T) -> R. For two parameters — (T, U) -> R, and so on up to 22 arguments.
A lambda is a concise notation { args -> body }, an anonymous function is fun(args): ReturnType { body }. A lambda cannot have a return without a label, an anonymous function can. Both can be passed to a higher-order function.
Avoid higher-order functions in hot loops without inline — each lambda creates an object. For performance-critical code, use inline fun. Also, do not overuse deeply nested lambdas — this reduces readability.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also