Part of Github Copilot with Kotlin

Kotlin with Copilot: Higher order add function

Sandy LaneSandy Lane

Video: Kotlin with Copilot: Higher order add function by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: Higher-Order Functions — Pass an Operation as a Parameter

A function that accepts another function as a parameter is higher-order. fun apply(a: Int, b: Int, op: (Int, Int) -> Int): Int = op(a, b). Pass +, -, * as values.

A small puzzle that demos Kotlin's first-class function support — the foundation of map, filter, callbacks, and event handlers.

The Copilot prompt

// A higher-order function that takes an operation and applies it to two integers
fun higherOrderAdd(a: Int, b: Int, operation: (Int, Int) -> Int): Int {

Copilot generates:

fun higherOrderAdd(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
  return operation(a, b)
}

fun main() {
  val add: (Int, Int) -> Int = { x, y -> x + y }
  val result = higherOrderAdd(5, 10, add)
  println("Result: $result")   // 15
}

Walkthrough

1. The function-type parameter. operation: (Int, Int) -> Int — a function taking two Ints and returning an Int. Just like a normal parameter type, except it describes a callable.

2. The body. operation(a, b) — invoke the function passed in. Same syntax as calling any function.

3. The lambda. { x, y -> x + y } is an anonymous function. The (Int, Int) -> Int type annotation tells Kotlin what type the lambda is.

4. The call. higherOrderAdd(5, 10, add) — three arguments: two ints and one function.

Function types

Kotlin's function-type syntax: (P1, P2, ...) -> R. Examples:

  • () -> Unit — no params, no return.
  • (Int) -> Int — takes Int, returns Int.
  • (String) -> Boolean — takes String, returns Boolean (a predicate).
  • (Int, Int) -> Int — takes two Ints, returns Int.

These are real types — var f: (Int) -> Int = { x -> x * 2 }. You can store them, pass them, return them.

Function references

For named functions, ::name produces a function value:

fun add(x: Int, y: Int): Int = x + y
fun subtract(x: Int, y: Int): Int = x - y

val addRef: (Int, Int) -> Int = ::add
println(higherOrderAdd(5, 10, ::add))      // 15
println(higherOrderAdd(5, 10, ::subtract)) // -5

::add is a function reference. Equivalent to { x, y -> add(x, y) } but cheaper — no lambda allocation.

Passing different operations

val add: (Int, Int) -> Int = { x, y -> x + y }
val subtract: (Int, Int) -> Int = { x, y -> x - y }
val multiply: (Int, Int) -> Int = { x, y -> x * y }

println(higherOrderAdd(5, 10, add))       // 15
println(higherOrderAdd(5, 10, subtract))  // -5
println(higherOrderAdd(5, 10, multiply))  // 50

Same higher-order function, three different behaviors via different lambdas.

Returning a function

A function can also return a function:

fun makeMultiplier(factor: Int): (Int) -> Int {
  return { x -> x * factor }
}

val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5))   // 10
println(triple(5))   // 15

makeMultiplier returns a closure that captures factor. Each call to makeMultiplier produces a new function with its own factor.

This is the factory pattern in functional clothes. Episode 16 of the Lua series covers the same idea.

Trailing-lambda syntax

When the last parameter is a function, Kotlin lets you write the lambda outside the parentheses:

val result = higherOrderAdd(5, 10) { x, y -> x + y }

Compare to:

val result = higherOrderAdd(5, 10, { x, y -> x + y })

Same call. The trailing-lambda form reads more naturally — most Kotlin code uses it.

This is why numbers.filter { it > 0 } works — filter takes one argument (the predicate); the lambda goes after the () (which is omitted when there are no other args).

inline for performance

Higher-order functions allocate a function object per call. For hot paths, mark the function inline:

inline fun higherOrderAdd(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
  return operation(a, b)
}

The compiler inlines the body at every call site, so no function object is allocated. Used everywhere in stdlib (map, filter, also, apply, etc.).

Don't inline huge functions — bytecode duplication. Reserve for small, frequently-called HOFs.

Common patterns: callbacks

fun loadUser(id: Int, onSuccess: (User) -> Unit, onError: (Throwable) -> Unit) {
  // ... async work ...
  if (...) onSuccess(user) else onError(error)
}

loadUser(
  id = 42,
  onSuccess = { user -> println("Loaded: $user") },
  onError = { e -> println("Failed: ${e.message}") },
)

Two function parameters, one for each outcome. Common in async APIs before suspend functions took over.

Standard library: fold, reduce, map, filter

The Kotlin stdlib is built on higher-order functions:

val sum = (1..10).fold(0) { acc, n -> acc + n }   // 55
val max = listOf(3, 1, 4, 1, 5).reduce { a, b -> if (a > b) a else b }   // 5
val doubled = listOf(1, 2, 3).map { it * 2 }       // [2, 4, 6]
val even = listOf(1, 2, 3, 4).filter { it % 2 == 0 } // [2, 4]

Each takes a function as an argument. The whole functional style of Kotlin is built on this.

Common mistakes

Calling instead of referencing. higherOrderAdd(5, 10, add(1, 2)) is wrong — that calls add first and passes its result (an Int, not a function). Pass add, not add(...).

Wrong function-type signature. (Int) -> Int for a 2-arg function won't compile. Match the parameter list exactly.

Forgetting inline for hot paths. Adds millions of lambdas/sec on inner loops.

return inside a lambda. Returns from the enclosing function, not from the lambda. Use a labelled return: return@filter to return from a filter lambda.

Confusing ::function with function(). ::name produces a function value. name() calls the function. Different semantics.

What's next

Episode 26: Lambda to print a full name from first + last. A two-arg lambda that produces side effects.

Recap

Higher-order functions take or return functions. Function type syntax: (P1, P2) -> R. Lambdas: { x, y -> x + y }. Function references: ::name. Trailing-lambda syntax f(args) { ... } is idiomatic. inline to avoid lambda allocation in hot paths. The whole Kotlin stdlib (map, filter, fold, reduce) is HOFs.

Next episode: lambda for full name.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.