In a previous post, I wrote about Kotlin Functions and, amongst other things, how they can be defined, named and then used by calling the function’s name. However, this is not the only way to define a function.
When you call a function like println()
, readLine()
, etc you are calling regular named functions. They have been previously defined using the fun
keyword and all have a name that is part of the function’s signature. BUT, you can also define a function without using the fun keyword OR giving it a name. Confused? Oh, it gets worse.
These little devils are called anonymous functions because they don’t have a name as part of their definition. They interact a little bit differently with the code. They are functions that can be passed or returned from other functions. And they are an essential part of Kotlin. They can be used, for example, to customize how the built-in functions from Kotlin Standard Library work, by specifying additional rules to meet your specific needs.
Let’s consider the function count
for example. This function is an existing function in the Kotlin Standard Library and it’s used to, well… count elements. You can use it to count the elements of a list for example, and it will return how many elements there are in the given list.

But what if you have the following list
val list1 = listOf(2, 8, 5, 7, 9, 6, 10)
and you need to know how many even numbers you have in there? You can do something like this:
fun main() {
val list1 = listOf(2, 8, 5, 7, 9, 6, 10)
val result = list1.count { it % 2 == 0 }
print("Number of even numbers in the list: $result")
And the output would be:
Number of even numbers in the list: 4
What is happening in the code above is basically this: We are calling the count function on list1
and passing as a parameter another function that says that we want to count only the items in the list that has modulus of 2 = 0
(which means it’s an even number) and storing it in a variable called result
.
We then print a string using the variable to present the achieved result. If you used the count()
function alone, without passing the { it % 2 == 0 }
expression it would simply count how many elements list1 has.

This piece of syntax is called a lambda expression λ (aka sometimes as a function literal):
{ it % 2 == 0 }
Function Type
Function Literals, or Lambdas, also have a type, just like we have data types. Kotlin uses function types for declarations that deal with functions. These types have a special notation that corresponds to the signatures of the functions for example:
(Int) -> String
Variables of the function type hold a function as their value, and the function can then be passed around your code like any other variable and you would have something like this:
val
variableName: (parameter) -> return type = { lambda function}
Now, let’s spice things up a bit. Consider the code below:
fun main() {
yell("this is Sparta")
}
fun yell(
message: String ){
val yellingMessage: () -> String = {
val numExclamation = 5
message.uppercase() + "!".repeat(numExclamation)
}
println(yellingMessage())
}

What’s wrong with this?
You might have noticed that there is no return keyword in the lambda defined above even though there’s a return type specified as a String. Mr. Compiler did not yell (despite the name of the function) and there’s an output that is actually a string. The string THIS IS SPARTA!!!!! in all caps plus the 5 exclamation points added at the end of the string provided as a parameter when you call the yell("this is Sparta")
function :
val yellingMessage: () -> String = {
val numExclamation = 5
message.uppercase() + "!".repeat(numExclamation)
}
As you might have noticed by now, lambda functions are not regular functions. It’s kind of like quantum physics. Nothing behaves the way you expected it to behave 💁♀️
Lambda expressions do not require (or even allow, except in rare situations) a return statement. It implicitly returns the last line of the function allowing us to omit the return key.
Well, young padawan, because having a return key would be ambiguous to the poor compiler who would not know if the return statement was coming from the lambda function or the function calling the lambda function 🤯
Arguments
Like a regular function, a Lambda can accept one, several or no arguments of any type. Let’s see how it works and just for argumentative sake, let’s imagine the data in the above case should be persistent, so let’s move it out of the yell()
function and declare it as a top-level variable:
fun main() {
yell("this is Sparta")
}
val yellingMessage: (String) -> String = { message ->
val numExclamation = 5
message.uppercase() + "!".repeat(numExclamation)
}
fun yell(
message: String
){
println(yellingMessage(message))
}
Here val yellingMessage: (String) -> String = { message ->
you specify that the lambda accepts a String by adding the type to the parenthesis
val yellingMessage: (String)
Just like before, you define the lambda will return a string
val yellingMessage: (String) -> String
Then you name the string parameter in the function here:
val yellingMessage: (String) -> String = { message ->
The body of the function goes after the second arrow ( -> ) right after the named parameter.
IT
Nope, not this one!
The it
keyword. The implicit name of a single parameter. It is very common for a lambda expression to have only one parameter, and when they do (and only if they do), the it keyword
is a very convenient way to specify the parameter name.
So the whole code above could be re-written using the it keyword
like this:
fun main() {
yell("this is Sparta")
}
val yellingModifier: (String) -> String = {
val numExclamation = 5
it.uppercase() + "!".repeat(numExclamation)
}
fun yell(
message: String
){
println(yellingModifier(message))
}
The it keyword
is convenient, especially if, like me, you have a lot of issues naming variables 🤣 it can save you some time. On the other hand, it’s not very descriptive when it comes to the data it represents, so be careful when using it. Make sure that even if you use it the code is still readable. In other words, It’s great for short expressions but not that great for super complex lambdas.
What about if I need multiple arguments?
You can certainly have that too, only not using the it keyword.
You can try the code below using the REPL tool either on IntelliJ or Android Studio (Tools – Kotlin – Kotlin REPL)
val yell: (String, String) -> String = { quote, mood ->
when (mood) {
"angry" -> {
val numExclamationPoints = 5
quote.uppercase() + "!".repeat(numExclamationPoints)
}
"happy" -> {
"YAY!! $quote".uppercase()
}
else -> quote.uppercase()
}
}
println(yell("you shall not pass", "angry"))
You now have two parameters you need to pass: the quote and the mood you are applying to the quote.
📝 Unlike regular functions, lambdas cannot have default arguments
Type Inference
Now, let’s do what Kotlin does best and let’s make this baby more concise to the point it might actually get hard to read, shall we? Joking 😏
Just like we don’t need to explicitly determine the type of a variable, as long as you are giving it a value, and the compile will “guess” the type based on the value you give to the variable, we also don’t need to do it when declaring lambda functions. So, we could write this:
val yellingMessage: () -> String = {
val numExclamation = 5
message.uppercase() + "!".repeat(numExclamation)
}
Like this:
val yellingMessage = {
val numExclamation = 5
message.uppercase() + "!".repeat(numExclamation)
}
Type inference also works when a lambda accepts one or more arguments, but we need to use a little trick. You see, the compiler needs some help figuring out what types you are using on your lambda.
So instead of determining the type of the parameter and what the function will return outside of the actual function like this:
val yellingModifier: (String) -> String = {
val numExclamation = 5
it.uppercase() + "!".repeat(numExclamation)
}
You must provide both the name and the type of each parameter in the lambda expression’s definition:
val yellingModifier = { message: String ->
val numExclamationPoints = 5
message.uppercase() + "!".repeat(numExclamationPoints)
}
This also means that the it keyword is not an option if you want to use type inference.
When a Function Accepts a Function
At the beginning of this post, we’ve seen how the count()
function accepts another function (the lambda) as a parameter to make it customizable and flexible.
Consider the following code:
fun main() {
hanSoloSays("Luke Skywalker returned to Tatooine in an attempt to rescue me from the gangster Jabba the Hutt")
changeMood()
hanSoloSays("I have a bad feeling about this ")
}
var moodModifier: (String) -> String = { it }
fun hanSoloSays(
message: String,
modifier: (String) -> String = { moodModifier(it) }
) {
println(modifier(message))
}
fun changeMood() {
val mood: String
val modifier: (String) -> String
when (Random.nextInt(1..5)) {
1 -> {
mood = "angry"
modifier = { message ->
message.replace(message, "@#$%@%$@#%$%@$ $@@#$%@%$#@%") + "!".repeat(7)
}
}
2 -> {
mood = "sleepy"
modifier = { message ->
message.lowercase().replace(" ", "...") + " ZZzzZzZzZZzZZZ"
}
}
3 -> {
mood = "happy"
modifier = { message ->
val exclamationPoints = message.length
"YAY! " + message.uppercase() + "woohooooooo" + "!".repeat(exclamationPoints)
}
}
4 -> {
mood = "uncertain"
modifier = { message ->
"Hummmmm... $message. I am not so sure..."
}
}
5 -> {
mood = "shocked"
modifier = { message ->
message + " ...wait...what????".uppercase()
}
}
else -> {
mood = "professional"
modifier = { message ->
message
}
}
}
moodModifier = modifier
println("Han Solo mood: $mood")
}
There’s a parameter called modifier
on the hanSoloSays()
function of type String and that returns a String. The parameter modifier: (String) -> String = { moodModifier(it) }
does the same thing the var moodModifier: (String) -> String = { it }
does, but it can be overridden.
To test that we can try to print the output in a different colour (most terminals support basic text styling -Android Studio’s and IntelliJ’s included ). We can use ANSI escape sequences for that. They look ugly as hell, but let’s try it just for the sake of fun.
We’ll use two different sequences. Let’s add them as a parameter of the function hanSoloSays()
fun main() {
hanSoloSays("Luke Skywalker returned to Tatooine in an attempt to rescue me from the gangster Jabba the Hutt", {message -> "\u001b[34m$message\u001b[0m"
})
changeMood()
hanSoloSays("I have a bad feeling about this ")
}
And just like that, despite the ugly, ugly code, you have part of the output in blue:

What the heck is going on in that nasty piece of code, you ask?
The first part \u001b[34m
says you want the text that follows this escape sequence to be blue. \u001b
indicates it’s an escape sequence and 34m indicates the colour. The [ represents the start of the command after the escape sequence indicator. Then you close it with \u001b[0m which means that you want the text after that to be the regular colour (the 0m part).
📝 The most basic terminals have a set of 8 different colours:
- Black: \u001b[30m.
- Red: \u001b[31m.
- Green: \u001b[32m.
- Yellow: \u001b[33m.
- Blue: \u001b[34m.
- Magenta: \u001b[35m.
- Cyan: \u001b[36m.
- White: \u001b[37m.
Trailing Syntax
When a function accepts another function type as the last parameter you can omit the parentheses around the lambda argument. IntelliJ will warn you about this:

So the code above can also be written like this:
fun main() {
hanSoloSays(
"Luke Skywalker returned to Tatooine in an attempt to rescue me from the gangster Jabba the Hutt"
) { message ->
"\u001b[34m$message\u001b[0m"
}
changeMood()
hanSoloSays("I have a bad feeling about this ")
}
In the same way the val result = list1.count ({ it % 2 == 0 })
can be written like val result = list1.count { it % 2 == 0 }
Function Inlining
Lambdas add a great deal of flexibility to your code, however, it’s not for free.
When you determine a lambda, it is represented as an instance of an object, regardless of which platform your Kotlin code is running on. Also, at runtime, your program performs memory allocations for all variables accessible to the lambda, and this behaviour comes with some memory costs. As a result, lambdas introduce memory costs that can in turn cause a performance impact – and, in general, performance impacts should be avoided.
But, fear not. There’s a way to optimize lambda functions and remove the weight they carry. It’s called function inlining. Inlining removes the need for your lambda to use an object instance and perform variable memory allocations for itself.
To inline a lambda, you mark the function that accepts the lambda using the inline keyword like this:
inline fun hanSoloSays(
message: String,
modifier: (String) -> String = { moodModifier(it) }
) {
println(modifier(message))
}
Now, instead of calling the hanSoloSays()
instantiating a lambda object, the compiler replicates (basically copies and pastes) the function body where the function is invoked. This is a quick way to get a performance boost.
Now, the million-dollar question: Why aren’t all functions inlined by default?
Because nothing is this simple.
There are some scenarios where it’s not possible to inline a function, like, for example, when the function is recursive (it calls itself) or when it uses functions or variables that have a more restrictive visibility scope than the function you want to inline.
Function Reference
There’s another way to use a function as an argument of a function and it’s called function reference. It converts a regular function, you know, the ones we create using the fun
keyword, into a value of function type and you can use it anywhere you use a lambda expression.
Let’s say you wrote the lambda that turns the text into yellow as a regular function:
private fun yellowText(message: String) = "\u001b[33;1m$message\u001b[0m"
You can reference this function and pass it as an argument like this:
fun main() {
hanSoloSays(
"Luke Skywalker returned to Tatooine in an attempt to rescue me from the gangster Jabba the Hutt", ::yellowText)
}
To refer to a function, you use the :: operator with the function name you would like a reference for.