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
Stringargument - it returns
Unit(no useful return value)
The function buz has this signature:
fun buz(m: String): Unit
This matches the same shape:
- one
Stringparameter - 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, andforEach - 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)
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:
main()callsfoo("a message", ::buz).- The first argument is the string
"a message". - The second argument is a reference to the function
buz. - Inside
foo, the parametermbecomes"a message". - Inside
foo, the parameterbarnow refers tobuz. fooexecutesbar(m).- Since
barrefers tobuz, this becomes effectively:
buz("a message")
buzprints:
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.
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
| Approach | Example | Best use | Notes |
|---|---|---|---|
| Lambda | foo("x") { println(it) } | Short one-off behavior | Great for inline logic |
| Function reference | foo("x", ::buz) | Reusing an existing function | Cleaner when the function already exists |
| Anonymous function | foo("x", fun(m: String) { println(m) }) | Rare cases needing explicit syntax | More verbose |
Function call vs function reference
| Syntax | Meaning |
|---|---|
buz("hello") | Calls the function now |
::buz | Refers to the function so it can be passed around |
Lambda vs named function
| Feature | Lambda | Named function |
|---|---|---|
| Defined inline | Yes | No |
| Reusable | Usually less reusable | Yes |
| Readability for short logic | Very good | Can be too much for tiny logic |
| Readability for shared logic | Can repeat code | Usually better |
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) -> Unitto declare a function parameter. - Use
::functionNameto pass a named function. - The parameter types and return type must match.
buz("x")calls the function.::buzpasses 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.
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
Stringmessage 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.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.