1. Overview

One notable feature that adds to Kotlin’s expressiveness is the ability to use functions as arguments, coupled with the convenience of default values.

In this tutorial, we’ll explore Kotlin’s default arguments, functions as arguments, and functions as arguments with default values.

2. Default Arguments

First, let’s quickly understand how the default arguments work through an example:

fun greeting(name: String = "Baeldung"): String {
    return "Hi $name"

The greeting() function has one name argument with the default value “Baeldung“. So, if we call the function without providing name, it uses the default value:

assertEquals("Hi Baeldung", greeting())

However, if we pass a string to it, the function uses it as name:

assertEquals("Hi Kai", greeting("Kai"))

As we can see, Kotlin’s default arguments save us from overloading functions or writing multiple function definitions with different parameter combinations. This leads to more concise code.

3. Functions as Arguments

Kotlin functions are first-class, meaning they can be assigned to variables and passed as arguments.

Next, let’s extend our greeting() function to add one more argument, which is a function type:

fun greeting1(name: String = "Baeldung", buildMessage: (String) -> String): String {
    return "Hi $name, ${buildMessage(name)}"

The greeting1() function’s second argument buildMessage is a function that takes a string as the argument and returns a string. Also, in greeting1()‘s body, we pass name to buildMessage() to create a message dynamically.

Next, let’s craft a straightforward function that can be used as the buildMessage function using the lambda expression:

val msgByCharCount: (String) -> String = { input: String ->
    "your name has an ${if (input.length % 2 == 1) "odd" else "even"} number of letters."

As the code above shows, msgByCharCount determines if the input string has an odd or even number of characters. Next, we can pass msgByCharCount as the buildMessage argument to greeting1() and get the expected results:

val msgWithDefaultName = greeting1(buildMessage = msgByCharCount)
assertEquals("Hi Baeldung, your name has an even number of letters.", msgWithDefaultName)

val kaiMsg = greeting1(name = "Kai", buildMessage = msgByCharCount)
assertEquals("Hi Kai, your name has an odd number of letters.", kaiMsg)

4. Function as the Argument With a Default Value

In greeting1(), although the buildMessage argument is with the function type since it’s an argument, it can have a default value, too. The default value will give the buildMessage function a default implementation. 

Next, let’s use a lambda expression as buildMessage‘s default value, which creates a message depending on the input string’s length:

fun greeting2(name: String = "Baeldung", buildMessage: (String) -> String = { input: String ->
    "your name is ${if (input.length > 3) "long" else "short"}."
}): String {
    return "Hi $name, ${buildMessage(name)}"

So, when we call greeting2(), we can skip passing a buildMessage function and use its default implementation:

val result1 = greeting2()
assertEquals("Hi Baeldung, your name is long.", result1)

val result2 = greeting2("Kai")
assertEquals("Hi Kai, your name is short.", result2)

Of course, if we have a particular requirement, we can pass our function as buildMessage, and greeting2() will take our function to create the message:

val result3 = greeting2(buildMessage = msgByCharCount)
assertEquals("Hi Baeldung, your name has an even number of letters.", result3)

val result4 = greeting2(name = "Kai", buildMessage = msgByCharCount)
assertEquals("Hi Kai, your name has an odd number of letters.", result4)

In some scenarios, we may want the default implementation to always return a constant value and ignore the input parameter. In this case, in the default lambda expression, we can use underscores for the parameters we don’t need, for example:

fun greeting3(name: String = "Baeldung", buildMessage: (String) -> String = { _ -> "how do you do?" }): String {
    return "Hi $name, ${buildMessage(name)}"

As greeting3()’s code shows, the default implementation of the buildMessage function is to return a fixed string, regardless of the input it receives.

Now, if we don’t pass buildMessage to greeting3(), “how do you do?” will be the default message:

val result1 = greeting3()
assertEquals("Hi Baeldung, how do you do?", result1)

val result2 = greeting3("Kai")
assertEquals("Hi Kai, how do you do?", result2)

5. Conclusion

In this article, we’ve learned Kotlin’s default arguments and functions as argument features. Further, we’ve explored how to make functions as arguments with default values.

Seamlessly integrating these features allows our code to be more adaptive, modular, and readable.

As always, the complete source code for the examples is available over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.