Kotlin Tutorial

Lambda Function in Kotlin (With Examples & Tutorial)

Table of Contents

  • Introduction
  • What is Lambda in Kotlin?
  • Kotlin Lambda Example
  • Type Inference in Kotlin Lambdas
  • Type Declaration in Kotlin Lambdas
  • Kotlin Lambda in Higher-Order Function
  • Important Concepts
  • Returning a Value From Lambda Expression in Kotlin
  • Difference Between Lambda and Anonymous Functions in Kotlin
  • Kotlin Lambda With Multiple Parameters
  • Kotlin Empty Lambda

Introduction

Kotin Lambda expression and Anonymous function are function literals, which means they are not declared but passed immediately as an expression.

This blog will explore more about lambda expression and anonymous functions in Kotlin and help you understand them efficiently with the help of a few examples

What is Lambda in Kotlin?

A lambda function in Kotlin (often referred to simply as a "lambda") is a concise way to define anonymous functions or blocks of code that can be treated as objects and passed as arguments to other functions. 

Lambdas are a fundamental feature of the language and are often used to achieve more concise and readable code.

Here's the basic syntax of a lambda in Kotlin:

{ parameters -> body }
  • parameters are the input parameters, if any.

  • body is the code that gets executed when the lambda is called

A lambda expression is surrounded by curly braces and argument declaration is mentioned within these curly braces. There are optional type annotations, and the code_body is placed after an arrow sign. 

If the inferred return type of a lambda expression is not Unit, the last expression in the lambda body is treated as a return value.

Kotlin Lambda Example

Here are a few examples of lambda in Kotlin:

1. Lambda with No Parameters:

val sayHello = { println("Hello, world!") }
sayHello() // Output: Hello, world!

In this example, sayHello is a lambda function that takes no parameters and simply prints "Hello, world!" when called.

2. Lambda with Parameters:

val add = { x: Int, y: Int -> x + y }
val result = add(3, 4) // result is 7

This lambda add takes two integer parameters, x and y, and returns their sum when called with add(3, 4).

3. Lambda in a Higher-Order Function:

val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it }
// squaredNumbers is [1, 4, 9, 16, 25]

Here, the map function is used with a lambda to square each element in the numbers list, resulting in squaredNumbers.

4. Lambda with a Predicate:

val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
// evenNumbers is [2, 4]

In this example, the filter function is used with a lambda to filter and select only the even numbers from the numbers list.

5. Lambda as a Function Argument:

fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}
val addition = operateOnNumbers(3, 4) { x, y -> x + y }
val multiplication = operateOnNumbers(3, 4) { x, y -> x * y }
// addition is 7, multiplication is 12

In this example, the operateOnNumbers function takes two integers and a lambda as arguments to perform different operations on them.

Type Inference in Kotlin Lambdas

Type inference in Kotlin lambdas is a feature that allows the Kotlin compiler to automatically determine the data types of lambda parameters and the return type, making your code more concise and less error-prone. Type inference reduces the need for explicit type annotations in your lambda expressions, resulting in more readable and maintainable code.

Here are some examples of type inference in Kotlin lambdas:

1. Lambda with Type Inference for Parameters:

val add = { x, y -> x + y }

In this example, the types of x and y are inferred by the compiler based on the context. The compiler recognizes that x and y are integers because they are used in a mathematical operation (x + y).

2. Lambda with Type Inference for Return Type:

val square = { x: Int -> x * x }

In this case, you provide the type annotation for the parameter x, but the return type is inferred by the compiler based on the expression x * x. Since multiplication results in an integer, the lambda's return type is also inferred as Int.

3. Lambda with Type Inference in Higher-Order Functions:

Type inference also works seamlessly with higher-order functions:

val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it }

In the map function, the compiler infers that the lambda { it * it } operates on elements of type Int (from the numbers list) and returns values of the same type. The return type of the lambda is inferred as Int.

4. Explicit Type Annotations:

While type inference is often sufficient, you can still provide explicit type annotations if needed:

val add: (Int, Int) -> Int = { x, y -> x + y }

In this example, you specify the types of the parameters and the return type explicitly. This can be helpful in situations where the lambda's type might not be immediately clear.

Type Declaration in Kotlin Lambdas

In Kotlin lambdas, you can explicitly declare the types of lambda parameters and the return type when you want to provide clarity, make the code more self-documenting, or handle situations where type inference may not work as expected. Explicit type declaration can be particularly useful in complex or ambiguous scenarios.

Here are examples of type declaration in Kotlin lambdas:

1. Lambda with Explicit Parameter Types:

val multiply: (Int, Int) -> Int = { x, y -> x * y }

In this example, the lambda multiply explicitly declares that it takes two integers as parameters (x and y) and returns an integer.

2. Lambda with Explicit Return Type:

val divide: (Double, Double) -> Double = { x, y -> x / y }

Here, the lambda divide specifies that it takes two doubles as parameters and returns a double. This is important for situations where you need precise control over the return type.

3. Lambda in a Higher-Order Function with Explicit Types:

fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}
val addition = operateOnNumbers(3, 4) { x: Int, y: Int -> x + y }
val subtraction = operateOnNumbers(3, 4) { x: Int, y: Int -> x - y }

In this example, the lambda expressions used with the operateOnNumbers function explicitly declare the types of the parameters x and y. This can be helpful for code readability and ensuring that the types match the expected function signature.

4. Lambda with Inferred Parameter Types and Explicit Return Type:

val calculate: (Int, Int) -> Double = { x, y -> (x + y).toDouble() }

In this case, the parameter types are inferred based on the usage of x and y, but the return type is explicitly declared as Double to ensure the result is a double.

Kotlin Lambda in Higher-Order Function

Kotlin supports the use of lambda expressions in higher-order functions, which are functions that can accept other functions as parameters or return functions as results. 

Lambda expressions in higher-order functions allow you to pass behavior as an argument to a function, which is a powerful concept in functional programming. 

Here are some examples of how to use lambda expressions in higher-order functions:

1. Passing a Lambda to a Higher-Order Function:

fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
    return operation(x, y)
}
val addition = executeOperation(5, 3) { a, b -> a + b }
val multiplication = executeOperation(5, 3) { a, b -> a * b }
println("Addition: $addition") // Output: Addition: 8
println("Multiplication: $multiplication") // Output: Multiplication: 15

In this example, the executeOperation function is a higher-order function that takes two integers and a lambda as its parameters. The lambda represents a binary operation (in this case, addition and multiplication). The lambda is passed as an argument when calling the executeOperation function.

2. Returning a Lambda from a Higher-Order Function:

fun operationSelector(choice: String): (Int, Int) -> Int {
    return when (choice) {
        "add" -> { x, y -> x + y }
        "subtract" -> { x, y -> x - y }
        else -> { x, y -> x * y }
    }
}
val addFunction = operationSelector("add")
val subtractFunction = operationSelector("subtract")
val result1 = addFunction(10, 5) // Result: 15
val result2 = subtractFunction(10, 5) // Result: 5

In this example, the operationSelector function is a higher-order function that takes a choice (a string) and returns a lambda based on that choice. Depending on the choice, it returns a lambda for addition, subtraction, or multiplication. The returned lambda is then used to perform the desired operation.

Important Concepts

  • maxBy() Function

The maxBy() function returns the first element and gives the largest value of the function or null in case there are no elements.

  • it: implicit name of a single parameter

In many cases, lambda contains a single parameter. We use ‘it’ to represent the single parameter we pass to the lambda expression.

  • maxBy() and startsWith() Function

We can use the two library functions- maxBy() and startsWith() to compute the maximum age of a person object starting with the letter S.

Is the starsWith() function begins with the specified character passed as an argument, it returns true.

Returning a Value From Lambda Expression in Kotlin

You can return a value from a lambda expression in Kotlin using the return keyword. However, it's important to note that the behavior of return within a lambda expression differs depending on whether the lambda is used in a regular function or a higher-order function.

1. Returning from a Lambda within a Regular Function:

When a lambda expression is used within a regular function (not a higher-order function), you can use return to exit the lambda and return a value from the enclosing function:

fun calculateSum() {
    val numbers = listOf(1, 2, 3, 4, 5)
    var sum = 0
    numbers.forEach { number ->
        if (number == 3) {
            return@forEach  // Exit the lambda and continue with the next iteration
        }
        sum += number
    }
    println("Sum: $sum")
}
calculateSum() // Output: Sum: 12

In this example, when the lambda encounters number == 3, the return@forEach statement is used to exit the lambda and continue with the next iteration of the loop. It does not exit the calculateSum function itself.

2. Returning from a Lambda within a Higher-Order Function:

Returning a value from a lambda within a higher-order function can be a bit more complex. The return statement inside a lambda will return from the lambda itself, not from the surrounding higher-order function. If you want to return from the higher-order function, you need to use a different approach, such as using a labeled return.

Here's an example:

fun findFirstEvenNumber(numbers: List<Int>): Int {
    numbers.forEach {
        if (it % 2 == 0) {
            return@forEach it  // Return the first even number
        }
    }
    return -1  // Return -1 if no even number is found
}
val numbers = listOf(1, 3, 5, 2, 7, 8)
val firstEven = findFirstEvenNumber(numbers)
println("First even number: $firstEven")  // Output: First even number: 2

In this example, we use a labeled return@forEach to return the first even number found in the list when it's encountered within the lambda.

Difference Between Lambda and Anonymous Functions in Kotlin

In Kotlin, both lambda expressions and anonymous functions are used to define anonymous, or unnamed, functions. 

However, there are some differences between the two in terms of syntax, usage, and behavior.

1. Lambda Expressions:

  • Concise Syntax: Lambda expressions provide a more concise and expressive syntax. They are typically used for short and simple functions.

val lambda: (Int, Int) -> Int = { a, b -> a + b }
  • Type Inference: Lambda expressions often allow for type inference, where the data types of parameters and the return type are inferred from the context. This makes them more concise.

  • Single-Expression Functions: Lambdas are suitable for single-expression functions. If the lambda contains only one expression, the result is automatically returned.

val square: (Int) -> Int = { it * it } // "it" refers to the single parameter
  • Used with Higher-Order Functions: Lambdas are commonly used with higher-order functions like map, filter, and forEach for functional programming.

val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it }

2. Anonymous Functions:

  • Verbose Syntax: Anonymous functions have a more verbose syntax. They are used for more complex functions or when you need to specify the types explicitly.

val anonFunction = fun(a: Int, b: Int): Int {
    return a + b
}
  • Type Annotation: Anonymous functions often require type annotations for parameters and return type when they are not explicitly specified.

val add: (Int, Int) -> Int = fun(x, y): Int {
    return x + y
}
  • Multi-Expression Functions: Anonymous functions can handle multi-expression functions, and you must use the return statement explicitly when returning a value.

val multiply = fun(x: Int, y: Int): Int {
    val result = x * y
    return result
}

  • Used in Limited Scenarios: Anonymous functions are used in scenarios where you need to specify explicit types, perform multi-step operations, or work with more complex logic.

Kotlin Lambda With Multiple Parameters

Kotlin lambdas can accept multiple parameters just like regular functions. When you define a lambda with multiple parameters, you specify the parameter list within the curly braces and use the -> symbol to separate the parameters from the lambda body. 

Here's an example of a Kotlin lambda with multiple parameters:

val add: (Int, Int) -> Int = { a, b -> a + b }
val result = add(3, 5)
println("Result: $result") // Output: Result: 8

In this example, the add lambda takes two integer parameters, a and b, and returns their sum. When you call the lambda with add(3, 5), it returns 8 as the result.

You can use lambdas with any number of parameters and define them as needed based on your specific use case. Here's an example of a lambda with three parameters:

val multiply: (Int, Int, Int) -> Int = { x, y, z -> x * y * z }
val result = multiply(2, 3, 4)
println("Result: $result") // Output: Result: 24

In this case, the multiply lambda takes three integer parameters and returns their product when called.

Kotlin's lambda syntax allows you to create concise, readable, and expressive functions with multiple parameters, which is particularly useful when working with higher-order functions or custom behavior for your code.

Kotlin Empty Lambda

You can create an empty lambda expression in Kotlin, which is essentially a lambda that doesn't contain any code in its body. An empty lambda is denoted by curly braces with no parameters and no code inside. 

It's often used as a placeholder or when you need a function that doesn't do anything, such as in some higher-order functions. 

Here's how you can define an empty lambda in Kotlin:

val emptyLambda: () -> Unit = {}

In this example:

  • emptyLambda is the name of the variable that holds the lambda.

  • () -> Unit specifies the lambda type. It's a lambda with no parameters (()) and a return type of Unit, which is similar to void in some other programming languages.

You can then call this empty lambda as you would any other lambda, even though it doesn't perform any specific action:

emptyLambda() // Calling the empty lambda with no effect
Did you find this article helpful?