Higher-order functions
A higher-order function accepts a function, returns a function, or does both.
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int =
operation(a, b)
val result = calculate(10, 5) { left, right -> left + right }
calculate controls when the operation runs. The caller supplies what the operation does.
This is useful when the steps stay the same but one rule changes. calculate always receives two numbers and invokes one operation; the caller chooses addition, subtraction, or another calculation.
15, followed by 5.Accepting behavior as a parameter
fun <T> findFirst(items: List<T>, matches: (T) -> Boolean): T? {
for (item in items) {
if (matches(item)) return item
}
return null
}
The function owns iteration. The caller provides the rule:
val firstLongName = findFirst(names) { name -> name.length > 6 }
This reduces duplicated control flow while keeping the changing rule visible.
Execution of findFirst(names) { name -> name.length > 6 }:
findFirststarts at the first item.- It calls
matches(item)for that item. - It returns immediately when the predicate is true.
- It returns null only if every item fails the test.
This short-circuit behavior matters: items after the first match are never tested.
Returning behavior
fun minimumLengthValidator(minimum: Int): (String) -> Boolean =
{ text -> text.length >= minimum }
val validPassword = minimumLengthValidator(8)
println(validPassword("android")) // true
The returned lambda remembers minimum. This is a closure: a function value plus the outside values it captured.
true, followed by false. The returned function remembers that minimum is 8.Collection operations are higher-order functions
val visibleNames = users
.filter { user -> user.isActive }
.sortedBy { user -> user.name }
.map { user -> user.name }
filterkeeps matching items.maptransforms every item.sortedBychooses the value used for ordering.any,all, andnoneanswer questions about a collection.foldcombines items into one accumulated result.
Avoid long chains that hide important decisions or create many intermediate collections in a performance-sensitive path. A clear loop is sometimes the better code.
Open in Kotlin Playground[Grace, Rakesh].Callback design
fun loadUser(
id: Long,
onSuccess: (User) -> Unit,
onError: (Throwable) -> Unit,
) { /* start work */ }
Callbacks are still useful for UI events and adapting callback-based APIs. For asynchronous operations that produce one result, a suspend function is often easier to compose and cancel than multiple callbacks.
The loadUser snippet is intentionally incomplete because thread choice, cancellation, and the actual data source depend on the Android application. It demonstrates the callback API shape; it is not presented as runnable Playground code.
Function composition
fun <A, B, C> compose(
first: (A) -> B,
second: (B) -> C,
): (A) -> C = { value -> second(first(value)) }
Composition is useful when each transformation has one clear job. Do not build an abstract pipeline when a direct function call is easier to follow.
Open in Kotlin Playground7. The string is trimmed first; the resulting string is then measured.Corner cases
A function parameter may be nullable
fun notify(message: String, listener: ((String) -> Unit)?) {
listener?.invoke(message)
}
Use a nullable callback only when “no listener” is a valid state. If the behavior is required, keep the parameter non-null so callers must provide it.
Open in Kotlin PlaygroundReceived: Saved, followed by Finished. The null listener performs no action.User(name=Rakesh, active=true), followed by true.Common mistakes
1. Several callbacks make the call site confusing
✕ Avoid unnamed meaning
loadUser(42, { show(it) }, { log(it) }, { hideLoading() })
✓ Name every behavior
loadUser(
id = 42,
onSuccess = { user -> show(user) },
onError = { error -> log(error) },
onComplete = { hideLoading() },
)
If the asynchronous flow keeps growing, prefer a suspend function or Flow instead of adding more callbacks.
2. A collection chain creates unnecessary intermediate lists
✕ Wasteful for a large list
val firstName = users
.filter { it.active }
.map { it.name }
.firstOrNull()
✓ Stop as soon as a match is found
val firstName = users.firstOrNull { user -> user.active }?.name
The first version can build filtered and mapped lists. The second searches until it finds one active user.
3. A generic abstraction hides simple business logic
✕ Unnecessary abstraction
fun <T, R> execute(value: T, operation: (T) -> R): R = operation(value)
val total = execute(price) { it * quantity }
✓ Say what the business operation does
fun orderTotal(price: Double, quantity: Int): Double = price * quantity
Use a higher-order function when it removes repeated control flow or creates a genuinely useful API—not merely because Kotlin allows it.
A higher-order function accepts or returns another function. Kotlin collection operators such as map and filter are common examples. They help separate stable control flow from caller-specific behavior, but too many callbacks or transformations can make execution and performance harder to understand.