RSS Amplifier

Ashraf Latif · Dec 17, 2025

Learning Kotlin: Lambda expressions

0
Sign in to vote or save

Ashraf Latif · Ashraf Latif


This is a normal function

fun uppercaseString(text: String): String {
    return text.uppercase()
}
fun main() {
   println(uppercaseString(”hello”))
}

it also can be written as a lambda expression:

func main() {
  val upperCaseString = {text: String -> text.uppercase()}
  println(upperCaseString("hello")) // HELLO
}

While `text` is the parameter and `String` is a type of the parameter.

it is almost the same as anonymous function in javascript.

const upperCaseString = text => text.toUpperCase();

lambda expression can also be passed to another function.

val numbers = listOf(1,-2,3,4,-5,6)
val positives = numbers.filters({n -> n > 0})
val negatives= numbers.filters{n -> n < 0} // remove parenthases allowed
println(positives) // [1,3,4,6]

Lambda expression can also be returned from a function. But let understand the function types first. Example from above code

val upperCaseString = {text: String -> text.uppercase()} 

Kotlin infer the function type of this line, but basically it was something like this

val upperCaseString: (String) -> String = {text -> text.uppercase()}

Where `upperCaseString` is the function name, `(String)` is the parameter type and String is the return type which is string.

This upperCaseString function received 1 parameter which is a string, and it return a string.

You must declare parameter and return types either in lambda expression or as a function type.

val upperCaseString = {text: String -> text.uppercase()} // delcared in the lambda
val upperCaseString: (String) -> String = {text -> text.uppercase()} // deleared as a function

this is not valid and will not work

val upperCaseString = {text -> text.uppercase()} // wont work as none was declared.

now let return lambda from a function

fun toSeconds(time: String): (int) -> Int = when(time){
  "hour" -> {value -> value * 60 * 60} // this return a lambda
  "minute" -> {value -> value * 60} // return a lambda
  else -> {value -> value} //return a lambda
fun main() {
  val timesInMinutes = listOf(2,10,15,1)
  val min2sec = toSeconds("minute")
  val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
  println("Total time is $totalTimeInSeconds secs") // Total time is 1680 secs
}

Lambda can be invoked separately by adding aprentheses `()` after the curly braces `{}` and including any parameters withing the parenthereses.

println({text: String -> text.uppercase()}("hello")) // HELLO

Trailing lambdas

println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 6

can also be written as

println(listOf(1, 2, 3).fold(0) { x, item -> x + item })  // 6

Example:

fun repeatN(n: Int, action: () -> Unit) {
  for(i in 1..n){
    action()
  }
}
  • n → an Int

  • action → a function

    • takes no params

    • returns Unit (like void)

So action is not a value, it is someting that can be called later. Inside `repeatN` we can see action(), kotlin call that function again and again.

how we call this function is

repeatN(5){
  println("hello")
} // this is how we do it as trailing lambda

basically kotlin rewrites this mentally as

repeatN(5, {println("hello")})

that `{ println(“hello“) }` is a lambda and this lambda matches the type () → Unit

  • No arguments

  • returns Unit

Why cannot do this

repeatN(5, println("Hello"))
  • println(“Hello”) runs immediately

  • prints `Hello` once

  • returns `Unit`

So basically we do

repeatN(5, Unit)

while `repeatN` wants a function. not `Unit`

it must be passed as a function not call it.

{ println("Hello") } 

A different mental model. Think of it like this

  • `println(“Hello”) → DO it now

  • `{println(“Hello“)} → SAVE it and do it later

Java/js equivalent

repeatN(5, () -> System.out.println("Hello"));  // java
repeatN(5, () => Console.log("Hello")); // js

if lambda is the only param

fun doSomething(action: () -> Unit) {
  action()
}
// can remove parentheses completelt
doSomething{
 println("Run!")
}
// instead of this
doSomething({ println("Run!") })

with multiple params

fun greet(name: String, action: ()-> Unit) {
  println("Hello $name")
  action()
}

Without trailing lambda

greet("Ashraf", { println("Welcome!") }
// Hello Ashraf
// Welcome!

with trailing lambda

greet("Ashraf"){
  println("Welcome!")
}

Why kotlin do trailing lambda like this? Because lambdas often contain multiple lines:

repeatN(5) {
  println("Hello")
  println("World")
  println("Again")
}

compare with this:

repeatN(5, {
  println(”Hello”)
  println(”World”)
  println(”Again”)
})

Trailing lamndas remove visual clutter.

Real kotlin examples that we will see everywhere

// repeat
repeat(3) {
    println(it)
}
// forEach
list.forEach {
    println(it)
}
// run
run {
    println("Inside block")
}
// Android / Compose / KMP
Button(onClick = {
    println("Clicked")
}) {
    Text("Click me")
}

how to declare the function that accept another function:

//not allowed
fun bad(a: () -> Unit, b: Int)
// allowed
fun good(b: Int, a: () -> Unit)

mental shortcut, when you see this

someFunction(...) {
    // code
}

This read as “I am passing a function as the last argument“

Some other usage of lambda in kotlin

fun someFun(n: Int, action: (String) -> Unit) {
   action("Hello from kotlin")
}

we can call it like this

someFun(3, { s -> println(s) })
// or
someFun(3) { s: String -> println(s) }
// or since the function declaration already have type
someFun(3) { s -> println(s) }
// or since it has only 1 param we can skip the param
someFun(3) { println(it) }

common mistake

fun sumFun(n: Int, action: (String, Int) -> Unit) {
    for (i in 1..n) {
        action()
    }
}

action here received no arguments while it expecting 2. a String and an Int

fun sumFun(n: Int, action: (String, Int) -> Unit) {
    for (i in 1..n) {
        action("Iteration", i)
    }
}
sumFun(5) { s, i ->
    println("$s $i")
}

Multi-parameter example that makes sense


fun repeatWithLabel(n: Int, label: String, action: (String, Int) -> Unit) {
    for (i in 1..n) {
        action(label, i)
    }
}
repeatWithLabel(3, "Step") { label, i ->
    println("$label $i")
}

can also use named parameter in function types

fun withUser(action: (name: String, age: Int) -> Unit) {
    action("Ashraf", 30)
}
withUser { name, age ->
    println("$name is $age")
}

Lambda that return values

fun calculate(a: Int, b: Int, action: (Int, Int) -> Int): Int {
    return action(a, b)
}
val sum = calculate(3, 4) { x, y ->
    x + y
}
println(sum) // 7
// or single-line
val product = calculate(3, 4) { x, y -> x * y }

We are also allowed to skip or ignore unused parameters using `_`

fun repeatWithLabel(n: Int, action: (String, Int) -> Unit) {
    for (i in 1..n) {
        action("Step", i)
    }
}

Ignore the `String`

repeatWithLabel(3) { _, i ->
    println(i)
}

Ignore the `Int`

repeatWithLabel(3) { label, _ ->
    println(label)
}

But then I thought if we want to ignore the param, why bother declaring it right? or is there a way to run a function based on the params exist or not. End up we are unable to do it and should not do it. thre are option to design it properly.

Nullable parameter

fun repeatWithLabel(
    n: Int,
    action: (String?, Int) -> Unit
) {
    for (i in 1..n) {
        action(null, i)
    }
}
// usage
repeatWithLabel(3) { label, i ->
    if (label != null) {
        println("$label $i")
    } else {
        println(i)
    }
}

Overload function which I prefer and very kotlin-idiomatic

fun repeatWithLabel(n: Int, action: (Int) -> Unit) {
    for (i in 1..n) {
        action(i)
    }
}
fun repeatWithLabel(n: Int, action: (String, Int) -> Unit) {
    for (i in 1..n) {
        action("Step", i)
    }
}
// usage
repeatWithLabel(3) {
    println(it)
}
repeatWithLabel(3) { label, i ->
    println("$label $i")
}

or other option that for complext data (this make my brain spinning), using data class

data class Step(val label: String, val index: Int)
fun repeatStep(n: Int, action: (Step) -> Unit) {
  for( i in 1..n ){
    action(Step("Step", i))
  }
}
// usage
repeatStep(3) {
    println(it.index)
}

No posts

Read the original on ceghap.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.