Kotlin Lambda Functions - Guruprasad Hegde - Medium
Guruprasad Hegde
13 min read
Aug 27, 2024
What are Lambda functions in Kotlin?
In Kotlin, lambda functions (or simply lambdas) are anonymous functions that can be treated as a data type, meaning they
can be passed as arguments to other functions, returned from functions, or assigned to variables. They are a key feature in
Kotlin and are often used in functional programming and in conjunction with higher-order functions.
Like other data types that you can express with literal values — such as an Int type of a 31 value and a String type of a
"Lambda" value—you can also declare function literals, which are called lambda expressions or lambdas for short. You
use lambda expressions extensively in Android development and more generally in Kotlin programming.
Lambda expression syntax:
A basic lambda expression in Kotlin looks like this:
val sum = { a: Int, b: Int -> a + b }
Here’s a breakdown of this lambda:
• { a: Int, b: Int -> a + b }: This is the lambda expression.
• a: Int, b: Int: These are the parameters of the lambda.
• ->: This separates the parameters from the body of the lambda.
• a + b: This is the body of the lambda, which in this case, returns the sum of a and b.
A lambda expression in Kotlin is enclosed in curly braces {}. Inside the braces, you declare parameters (with optional
type annotations), followed by an arrow ->, and then the code body. If the return type isn’t Unit, the last expression in the
body is automatically treated as the return value.
Here are some key characteristics of lambdas:
• Anonymous: They don’t have a name like regular functions.
• Function literals: Defined without the fun keyword.
• Expressions: Passed as part of an expression, often as arguments to higher-order functions.
• Concise syntax: Uses curly braces {} to enclose the function body.
There are four main variations of function types, depending on whether they
take parameters and whether they return a value.
1. Function Type with Parameters and a Return Type
This type of function takes one or more parameters and returns a value.
Syntax:
(ParameterType1, ParameterType2, ...) -> ReturnType
Example:
val sum: (Int, Int) -> Int = { a, b -> a + b }
• Parameters: Two Int values (a and b).
• Return Type: Int (the result of adding a and b).
2. Function Type with Parameters and No Return Type (Unit)
This function type takes one or more parameters but does not return a value (Unit is the Kotlin equivalent of void in other
languages).
Syntax:
(ParameterType1, ParameterType2, ...) -> Unit
Example:
val printSum: (Int, Int) -> Unit = { a, b -> println("Sum: ${a + b}") }
• Parameters: Two Int values (a and b).
• Return Type: Unit (the function only prints the result)
3. Function Type with No Parameters and a Return Type
This function type doesn’t take any parameters but returns a value.
Syntax:
() -> ReturnType
Example:
val greet: () -> String = { "Hello, World!" }
• Parameters: None.
• Return Type: String (the function returns a greeting).
4. Function Type with No Parameters and No Return Type (Unit)
This is a function type that takes no parameters and returns nothing (Unit).
Syntax:
() -> Unit
Example:
val sayHello: () -> Unit = { println("Hello!") }
• Parameters: None.
• Return Type: Unit (the function only prints "Hello!").
it Keyword: Used for Single Parameter
In Kotlin, the it keyword is a shorthand way to refer to a single parameter in a lambda expression. When a lambda has
only one parameter, Kotlin automatically provides this parameter as it, so you don't have to explicitly declare it. This
makes the lambda more concise and readable.
1. Using it in Collection Operations
• Commonly used in operations like map, filter, forEach, etc.
• Example:
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println(doubled)
Explanation: In this example, each element in the numbers list is referred to as it within the lambda, and it is multiplied
by 2.
2. Using it in Higher-Order Functions
• When passing a lambda as an argument, if the lambda has one parameter, it is used implicitly.
• Example:
fun performOperation(operation: (Int) -> Int): Int {
return operation(5)
}
val result = performOperation { it * 3 }
println(result)
Explanation: The lambda passed to performOperation takes a single Int parameter, which is referred to as it inside the
lambda. The lambda multiplies it by 3.
3. Using it in UI Configuration
• Common in Android development when setting up UI elements dynamically.
• Example
button.setOnClickListener {
Toast.makeText(context, "Button clicked!", Toast.LENGTH_SHORT).show()
}
Explanation: The setOnClickListener function takes a lambda with a single View parameter. The it keyword implicitly
refers to the clicked button.
When Not to Use 'it'
• Multiple Parameters: If your lambda has more than one parameter, you need to explicitly name the parameters,
and it cannot be used.
val sum = { a: Int, b: Int -> a + b }
• Explicit Naming for Clarity: Even with a single parameter, you might choose to explicitly name the parameter
instead of using it if it improves readability or clarity.
val printMessage = { message: String -> println(message) }
How do Lambda functions differ from regular functions in Kotlin
Lambda functions and regular functions in Kotlin serve similar purposes — they both encapsulate blocks of code that can
be executed later — but they have some key differences in terms of syntax, usage, and features.
1. Syntax
Regular Functions:
• Defined using the fun keyword, with a name, parameters, and a return type.
• Example
fun add(a: Int, b: Int): Int {
return a + b
}
Lambda Functions:
• Anonymous functions without a name, defined using curly braces {}.
• Parameters are declared within the braces, followed by the -> symbol, and the body of the lambda.
• Example
val add = { a: Int, b: Int -> a + b }
2. Usage Context
Regular Functions:
• Typically used when you need to define a reusable block of code that has a clear purpose and needs to be invoked
from multiple places.
• Can be top-level, member functions (inside a class or object), or local (inside another function).
Lambda Functions:
• Often used when you need a quick, inline function, especially when passing a function as an argument to a higher-
order function.
• They are more concise and are usually used in situations where defining a regular function would be overkill.
3. Naming
Regular Functions:
• Always have a name, which is used to invoke the function.
• Example:
val result = add(3, 4)
Lambda Functions:
• Anonymous and usually defined in place. They can be assigned to a variable, but the variable name is not the
function’s name.
• Example
val result = { a: Int, b: Int -> a + b }(3, 4)
4. Return Type
Regular Functions:
• Must specify a return type (either explicitly or inferred by the compiler).
• Example
fun multiply(a: Int, b: Int): Int {
return a * b
}
Lambda Functions:
• The return type is inferred from the expression in the body of the lambda. You don’t explicitly declare the return
type.
• Example
val multiply = { a: Int, b: Int -> a * b }
5. Receiver Type
Regular Functions:
• In regular functions, the receiver (if any) is declared in the function’s signature.
• Example
fun String.addExclamation(): String {
return this + "!"
}
Lambda Functions:
• Lambdas can have a receiver, making them extension-like functions, often used with Kotlin’s scope functions (let,
apply, run, etc.).
• Example
val addExclamation: String.() -> String = { this + "!" }
6. Return Behavior
Regular Functions:
• Can use the return keyword to exit the function and return a value.
• Example
fun divide(a: Int, b: Int): Int {
if (b == 0) return 0
return a / b
}
Lambda Functions:
• The return keyword inside a lambda expression refers to returning from the enclosing function, not just the
lambda. To return from the lambda itself, you can use a labeled return or avoid using return (by just letting the
lambda's last expression be the result).
• Example
val divide = { a: Int, b: Int ->
if (b == 0) 0 else a / b
}
7. Performance
• Regular Functions:
Compiled to regular methods, which can sometimes be slightly more optimized by the compiler.
• Lambda Functions:
May involve some overhead, especially when used heavily or in performance-critical code, due to their inline nature and
anonymous class creation.
• Regular functions are best for reusable, named pieces of code that have a specific purpose.
• Lambda functions are great for concise, inline code blocks, especially when working with higher-order functions
and functional programming paradigms in Kotlin.
Use cases of Lambda Function:
Lambda functions in Kotlin have a wide range of use cases, particularly in scenarios where you need to pass behavior as a
parameter, work with collections, or streamline repetitive operations. Here are some common use cases
1. Higher-Order Functions
• Passing Behavior as a Parameter: Lambda functions are often used as arguments to higher-order functions,
allowing you to pass behavior (like transformations, filtering, or processing logic) as a parameter.
• Example: Sorting a list of objects by a specific attribute.
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 30), Person("Bob", 25))
val sortedByAge = people.sortedBy { it.age }
println(sortedByAge)
2. Collection operations
• Mapping, Filtering, Reducing: Lambdas are commonly used with Kotlin’s collection functions like map, filter,
reduce, forEach, etc., to perform operations on collections.
• Example: Filtering even numbers from a list
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers)
3. Scope Functions
• let, apply, run, with, also: Scope functions use lambdas to operate on objects within a specific scope, often used
to initialize objects, perform operations, or handle nullability.
• Example: Initializing and configuring an object
val person = Person("John", 25).apply {
println("Person's name is $name and age is $age")
}
4. Event Handling
• Callbacks: Lambdas are useful for handling events, such as button clicks in Android development, where you pass
a lambda to handle the event.
• Example: Handling a button click in Android
button.setOnClickListener {
println("Button clicked!")
}
6. Asynchronous Programming
• Callbacks and Continuations: Lambdas are commonly used to handle asynchronous operations, such as making
network requests or processing data in the background.
• Example: Handling the result of an asynchronous operation
fun fetchData(callback: (String) -> Unit) {
callback("Data fetched")
}
fetchData { result ->
println(result)
}
7. Inline Functions and Reusability
• Code Reusability: Lambdas help create reusable blocks of code that can be passed around and executed in
different contexts, often used with inline functions to reduce overhead.
• Example: Creating a reusable logging function
inline fun logExecution(action: () -> Unit) {
println("Action started")
action()
println("Action finished")
}
logExecution {
println("Performing the action")
}
9. Extension Functions
• Adding Behavior to Existing Classes: You can use lambdas with receiver types to add new functionality to
existing classes, making the code more expressive and concise.
• Example: Adding a custom operation to a list
fun List<Int>.sumOfSquares(): Int {
return this.sumOf { it * it }
}
val result = listOf(1, 2, 3).sumOfSquares()
println(result)
10. UI Development
• Adding Behavior to Existing Classes: You can use lambdas with receiver types to add new functionality to
existing classes, making the code more expressive and concise.
• Example: Adding a custom operation to a list
val textView = TextView(context).apply {
text = "Hello, Kotlin!"
textSize = 16f
}
How lambda function return the last statement and how does it work
internally?
In Kotlin, lambda functions automatically return the value of their last expression, which simplifies their
syntax and usage. This behavior is a key feature of lambda expressions and is rooted in how Kotlin handles
expressions and returns in lambdas.
How Lambda Functions Return the Last Statement
In a lambda function, you don’t need to explicitly use the return keyword to return a value. The value of the last
expression in the lambda's body is returned automatically. Here’s how it works
val sum = { a: Int, b: Int -> a + b }
{ a: Int, b: Int -> a + b }:
• a + b is the last (and only) expression in this lambda.
• The result of a + b is automatically returned when the lambda is invoked
Example with Multiple Statements
If the lambda has multiple statements, the last one is returned:
val processNumbers = { a: Int, b: Int ->
val sum = a + b
val product = a * b
product
}
val result = processNumbers(3, 4)
product: This is the last expression in the lambda, so its value (which is a * b) is returned
Internal Mechanism: How It Works
Internally, Kotlin compiles lambda expressions into instances of anonymous classes that implement functional interfaces
(like FunctionN, where N is the number of parameters). The return value of the lambda is determined by the value of the
last expression, which becomes the return value of the invoke method of this anonymous class.
Consider the following lambda
val add = { a: Int, b: Int -> a + b }
Internally, it might look something like this (simplified for understanding):
val add = object : Function2<Int, Int, Int> {
override fun invoke(a: Int, b: Int): Int {
return a + b
}
}
1. Lambda as an Anonymous Class:
• When a lambda is defined, Kotlin creates an anonymous class that implements the corresponding functional
interface.
• For example, the lambda { a: Int, b: Int -> a + b } is compiled to an anonymous class that implements the
Function2<Int, Int, Int> interface.
2. Invoke Method:
• This anonymous class has an invoke method that takes the lambda’s parameters as arguments.
• The body of the lambda is executed inside this invoke method.
3. Returning the Last Expression:
• During the execution of the invoke method, the last expression in the lambda’s body is evaluated, and its result is
returned by the invoke method.
• Kotlin does this automatically, so you don’t have to explicitly specify a return statement
When you invoke add(3, 4), Kotlin calls the invoke method on this anonymous class, passing 3 and 4 as arguments. The
invokemethod executes a + b and returns the result.
Performance Consideration
Kotlin uses inlining for lambdas passed to higher-order functions marked with the inline keyword. When a
lambda is inlined, its code is directly inserted at the call site, avoiding the overhead of creating an
anonymous class.
For non-inlined lambdas, the anonymous class approach is used, which involves a small amount of overhead
due to object creation and method invocation.
Non-Technical Example: Ordering Coffee
Imagine you’re in a coffee shop, and you have a friend who is really good at remembering and placing orders. Instead of
giving them specific instructions on how to order each time, you give them a general task to handle the ordering for you.
You tell them, “Whenever I want a coffee, just order it how I like it.”
In Kotlin, this would be like defining a lambda function that takes care of ordering your coffee.
1. Defining the Task (Lambda Function)
• You tell your friend, “Whenever I say I want a coffee, just order a cappuccino with extra foam.”
• In Kotlin, this is like defining a lambda
val orderCoffee = { println("Ordering a cappuccino with extra foam!") }
2. Using the Task (Calling the Lambda):
• When you’re ready for coffee, you simply tell your friend, “Order my coffee.”
• In Kotlin, you would call the lambda
orderCoffee()
3. Handling Different Orders (Lambda with Parameters)
• Maybe sometimes you want a different kind of coffee. So, you modify your instruction: “Whenever I say I want
coffee, just ask me what kind, and then order it.”
• In Kotlin, this is like a lambda with parameters
val orderCoffee = { type: String -> println("Ordering a $type!") }
• Now, when you tell your friend, “Order my coffee,” you can specify what kind you want
orderCoffee("latte")
orderCoffee("espresso")
4. Getting a Result (Returning a Value)
• Suppose you want to know the price of the coffee after your friend orders it. You could instruct them, “Order my
coffee, and then tell me the price.”
• In Kotlin, the lambda could return a value
val orderCoffee = { type: String ->
println("Ordering a $type!")
5.0
}
val price = orderCoffee("cappuccino")
println("The price is $$price")
Understanding the concept of Coffee
• Lambda Functions: Think of them as simple instructions or tasks that you can give to someone (or your program)
to carry out. You can define them once, and then reuse them whenever you need that task done.
• Parameters: If you need some flexibility, you can ask for specific details (like what type of coffee), just like how
you can pass parameters to a lambda.
• Returning Values: Just like your friend could tell you the price of the coffee after ordering, a lambda can return a
value after doing its work.
Anonymous Function
Anonymous functions in Kotlin are similar to lambda expressions but provide additional flexibility, especially when it
comes to specifying return types and using return statements. While lambda expressions are generally more concise,
anonymous functions offer features that are useful in certain scenarios.
The body of the anonymous function can be either an expression or block.
1. Function body as an expression
fun(a: Int, b: Int) : Int = a * b
2. Function body as a block
fun(a: Int, b: Int): Int {
val mul = a * b
return mul
}
Key Characteristics of Anonymous Functions
1. Syntax :
• An anonymous function is defined using the fun keyword, followed by the function's parameters, return type, and
body.
• Example
val multiply = fun(a: Int, b: Int): Int {
return a * b
}
println(multiply(2, 3))
2. Return Types
• Unlike lambdas, you can explicitly specify the return type of an anonymous function. This is particularly useful
when the type cannot be inferred or when you want to make the return type clear.
• Example
val add = fun(a: Int, b: Int): Int {
return a + b
}
3. Usage of return
• In an anonymous function, the return keyword behaves as it would in a regular function, returning from the
anonymous function itself rather than the enclosing function. This contrasts with lambdas, where return can be
non-local if used in an inline function.
• Example
fun doOperation(x: Int, operation: (Int) -> Int): Int {
return operation(x)
}
val result = doOperation(5, fun(x: Int): Int {
return if (x > 0) x * 2 else x
})
println(result)
4. No Implicit it
• Unlike lambdas, anonymous functions do not have an implicit it parameter for single-parameter functions. You
need to explicitly define the parameter name.
• Example
val isEven = fun(num: Int): Boolean {
return num % 2 == 0
}
println(isEven(4))
5. When to Use Anonymous Functions
• Control Flow with return: If you need to return from the function explicitly, especially within loops or other
complex structures, anonymous functions are more appropriate.
• Explicit Return Type: When you need to clearly define or specify the return type of a function, an anonymous
function can be more readable.
• Multiple Lines of Code: For longer or more complex logic where a lambda might become hard to read, an
anonymous function can be clearer.
6. Comparison with Lambdas
• Lambdas: More concise, better suited for short and simple functions, can use implicit it for single parameters, and
can have non-local returns in inline functions.
• Anonymous Functions: More flexible with control over return types and explicit return, no implicit it, and better
for more complex logic.
Happy coding !!