Question

Passing Functions as Parameters in Kotlin

Question

You have a Kotlin function that accepts another function as a parameter:

fun foo(m: String, bar: (String) -> Unit) {
    bar(m)
}

You can call it with a lambda:

foo("a message", { println("this is a message: $it") })

// or using trailing lambda syntax
foo("a message") { println("this is a message: $it") }

Now suppose you also have this function:

fun buz(m: String) {
    println("another message: $m")
}

Can you pass buz as the function argument to foo instead of writing a lambda inline?

For example, can something like this work?

foo("a message", buz)

Short Answer

By the end of this page, you will understand how Kotlin treats functions as values, how to pass a named function to another function, and when to use a lambda versus a function reference such as ::buz. You will also learn the function type rules that must match for this to work.

Concept

In Kotlin, functions are first-class values. That means you can:

  • store them in variables
  • pass them to other functions
  • return them from functions

Your function foo expects a parameter named bar with this type:

(String) -> Unit

That type means:

  • it takes one String argument
  • it returns Unit (no useful return value)

The function buz has this signature:

fun buz(m: String): Unit

This matches the same shape:

  • one String parameter
  • returns Unit

So yes, buz can be passed to foo, but in Kotlin you usually pass a function reference, not just the bare function name.

The syntax is:

foo("a message", ::buz)

The :: operator means “refer to this function as a value.”

This matters in real programming because higher-order functions are everywhere in Kotlin:

  • collection operations like map, filter, and forEach
  • callbacks
  • event handlers
  • validation logic
  • reusable processing steps

Understanding function references helps you write cleaner and more reusable Kotlin code.

Mental Model

Think of a function like a tool in a toolbox.

  • A lambda is like creating a tool on the spot.
  • A named function is like using a tool you already built and labeled.
  • A function reference (::buz) is like handing someone the labeled tool so they can use it later.

So foo is saying:

Give me a message and also give me a tool that knows what to do with a String.

You can give it:

  • a brand new tool:
foo("hello") { println(it) }
  • or an existing tool by reference:
foo("hello", ::buz)
Take Quiz

Syntax and Examples

Core syntax

A function type in Kotlin looks like this:

(String) -> Unit

A function that accepts another function:

fun foo(m: String, bar: (String) -> Unit) {
    bar(m)
}

A named function with a matching signature:

fun buz(m: String) {
    println("another message: $m")
}

Pass the named function using a function reference:

foo("a message", ::buz)

Lambda vs function reference

Using a lambda

foo("a message") { println("this is a message: $it") }

Using a named function

fun buz(m: String) {
    println("another message: $m")
}

foo("a message", ::buz)

Both are valid because both provide something that matches (String) -> Unit.

Another example

fun greet(name: String, action: (String) -> Unit) {
    action(name)
}

fun printUpper(text: String) {
    println(text.uppercase())
}

fun main() {
    greet("kotlin", ::printUpper)
}

Output

KOTLIN

::printUpper is a reference to the existing function, so greet can call it later.

Step by Step Execution

Consider this example:

fun foo(m: String, bar: (String) -> Unit) {
    bar(m)
}

fun buz(m: String) {
    println("another message: $m")
}

fun main() {
    foo("a message", ::buz)
}

Here is what happens step by step:

  1. main() calls foo("a message", ::buz).
  2. The first argument is the string "a message".
  3. The second argument is a reference to the function buz.
  4. Inside foo, the parameter m becomes "a message".
  5. Inside foo, the parameter bar now refers to buz.
  6. foo executes bar(m).
  7. Since bar refers to buz, this becomes effectively:
buz("a message")
  1. buz prints:
another message: a message

This is the key idea: foo does not care whether bar came from a lambda or a named function. It only cares that the function type matches.

Real World Use Cases

Higher-order functions and function references are common in real Kotlin code.

1. Processing collections

val names = listOf("ana", "bob", "carl")
println(names.map(String::uppercase))

Here String::uppercase is a function reference.

2. Validation rules

fun validate(input: String, rule: (String) -> Boolean): Boolean {
    return rule(input)
}

You can pass different validation functions depending on context.

3. Logging or formatting callbacks

fun processMessage(message: String, logger: (String) -> Unit) {
    logger(message)
}

This lets you swap out how messages are handled.

4. Event handling

UI frameworks and asynchronous code often expect functions to run later when something happens.

5. Reusable business rules

If several parts of an app use the same transformation or output logic, a named function can be passed around instead of repeating the lambda.

Take Quiz

Real Codebase Usage

In real projects, developers use this concept to keep logic reusable and easy to test.

Common patterns

Guarded processing

fun withNonEmptyText(text: String, action: (String) -> Unit) {
    if (text.isBlank()) return
    action(text)
}

This uses an early return before calling the provided function.

Reusing named functions

fun logMessage(message: String) {
    println("LOG: $message")
}

fun handle(message: String, callback: (String) -> Unit) {
    callback(message)
}

handle("Started", ::logMessage)

This is clearer than repeating the same lambda in multiple places.

Passing behavior into utilities

fun transformAndPrint(value: String, transformer: (String) -> String) {
    println(transformer(value))
}

This style is very common in utilities and service layers.

Test-friendly design

When behavior is passed in as a function, it is easier to replace in tests.

fun send(data: String, output: (String) -> Unit) {
    output(data)
}

A test can pass a fake function to observe what happens.

Why developers like function references

  • less repetition
  • easier to read when a function already exists
  • encourages reusable small functions
  • works well with collection APIs and callbacks

Common Mistakes

1. Forgetting :: for a named function

This is the main issue in the original question.

Incorrect

foo("a message", buz)

Correct

foo("a message", ::buz)

Use :: when passing a named function as a value.

2. Function signature does not match

If foo expects (String) -> Unit, the function must accept one String and return Unit.

Broken example

fun wrongBuz(m: Int) {
    println(m)
}

foo("a message", ::wrongBuz)

This fails because Int is not String.

3. Returning the wrong type

Broken example

fun returnsNumber(m: String): Int {
    return m.length
}

foo("a message", ::returnsNumber)

This fails because foo expects a function returning Unit.

4. Confusing calling a function with referencing a function

Calling the function immediately

buz("hello")

Passing the function itself

::buz

These are not the same.

5. Assuming lambdas and function references are always identical

They often behave similarly, but function references reuse existing functions, while lambdas define behavior inline. Choose the one that makes the code clearer.

Comparisons

ApproachExampleBest useNotes
Lambdafoo("x") { println(it) }Short one-off behaviorGreat for inline logic
Function referencefoo("x", ::buz)Reusing an existing functionCleaner when the function already exists
Anonymous functionfoo("x", fun(m: String) { println(m) })Rare cases needing explicit syntaxMore verbose

Function call vs function reference

SyntaxMeaning
buz("hello")Calls the function now
::buzRefers to the function so it can be passed around

Lambda vs named function

FeatureLambdaNamed function
Defined inlineYesNo
ReusableUsually less reusableYes
Readability for short logicVery goodCan be too much for tiny logic
Readability for shared logicCan repeat codeUsually better
Take Quiz

Cheat Sheet

// Function that accepts another function
fun foo(m: String, bar: (String) -> Unit) {
    bar(m)
}

// Named function
fun buz(m: String) {
    println(m)
}

// Pass lambda
foo("hello") { println(it) }

// Pass named function
foo("hello", ::buz)

Rules

  • Use a function type like (String) -> Unit to declare a function parameter.
  • Use ::functionName to pass a named function.
  • The parameter types and return type must match.
  • buz("x") calls the function.
  • ::buz passes a reference to the function.

Common matching examples

fun printText(s: String) { println(s) }     // matches (String) -> Unit
fun length(s: String): Int = s.length       // matches (String) -> Int
fun noArgs() { }                            // matches () -> Unit

Quick pattern

fun runAction(value: String, action: (String) -> Unit) {
    action(value)
}

fun show(value: String) {
    println(value)
}

runAction("Hi", ::show)

FAQ

Can I pass a named function as a parameter in Kotlin?

Yes. Use a function reference with ::, such as ::buz.

Why does foo("a message", buz) not work?

Because Kotlin needs a function reference when you want to pass the function itself as a value. Use foo("a message", ::buz).

What does (String) -> Unit mean in Kotlin?

It means a function that takes one String argument and returns Unit.

When should I use a lambda instead of a function reference?

Use a lambda for short one-time logic. Use a function reference when a suitable named function already exists.

Does the function signature need to match exactly?

Yes. The number of parameters, their types, and the return type must be compatible.

What is the difference between buz("x") and ::buz?

buz("x") executes the function immediately. ::buz creates a reference you can pass to another function.

Take Quiz

Mini Project

Description

Build a small message processor that accepts a message and a handler function. This demonstrates how to pass different behaviors into the same function, including both lambdas and named functions. It mirrors real applications where the same input might be logged, formatted, validated, or saved differently.

Goal

Create a Kotlin program where one processing function can send a message to different handler functions.

Requirements

  • Create a function that accepts a String message and a handler of type (String) -> Unit.
  • Create at least two named handler functions with the same signature.
  • Call the processor once with a function reference and once with a lambda.
  • Print different outputs so the behavior change is visible.
Take Quiz

Keep learning

Related questions

View more