Chapter 21: Lambdas & Higher-Order Functions
Lambdas & Higher-Order Functions — this is the chapter where Kotlin starts feeling like pure magic and you’ll understand why so many developers fall in love with it! ☕🚀
Lambdas and higher-order functions are the heart of modern Kotlin. They make your code shorter, cleaner, more expressive, and they power almost everything cool in Kotlin (Collections operations, Coroutines, Android UI, Spring Boot DSLs, etc.).
We’re going to go super slowly, like we’re sitting together in a quiet Bandra café — I’ll explain every single concept with real-life analogies, lots of complete runnable examples, step-by-step breakdowns, tables, common mistakes with fixes, and fun facts so everything sticks perfectly.
Let’s dive in!
1. What is a Lambda Expression?
A lambda is a small, anonymous function — you write the function body inline without giving it a name.
Real-life analogy: Instead of writing a full recipe card (named function), you just whisper to the chef: “Take two eggs, mix with flour, and bake for 20 minutes” → that’s a lambda!
Basic syntax (all possible forms):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 1. No parameters () -> println("Hello! I’m a lambda!") // 2. One parameter (no parentheses needed!) { name -> println("नमस्ते $name!") } // 3. Multiple parameters { a: Int, b: Int -> a + b } // 4. With block body (multiple lines) { x: Int, y: Int -> println("Adding $x and $y") x + y } // 5. Single expression (implicit return – most common!) { x, y -> x + y } |
Key points:
- Lambdas are always wrapped in curly braces {}
- The last expression is the return value (no return needed)
- You can omit parameter types if Kotlin can infer them
- You can omit parameter names and use it for single parameter
2. Function Types – What a Lambda “Is”
Every lambda has a function type — Kotlin sees lambdas as objects that implement a special interface.
Common function types:
| Function Type | Meaning | Example Lambda |
|---|---|---|
| () -> Unit | No params, no return | { println(“Hi”) } |
| (String) -> Unit | One String param, no return | { name -> println(“Hello $name”) } |
| (Int, Int) -> Int | Two Int params, returns Int | { a, b -> a + b } |
| (String) -> String | One String in, one String out | { it.uppercase() } |
Example – Declaring function types
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fun main() { // Function type variable val greet: (String) -> Unit = { name -> println("नमस्ते $name!") } greet("Webliance") // नमस्ते Webliance! // Shorter with 'it' val shout: (String) -> String = { it.uppercase() + "!!!" } println(shout("Kotlin")) // KOTLIN!!! } |
3. Trailing Lambdas – The Most Beautiful Syntax
When a function’s last parameter is a lambda, you can move the lambda outside the parentheses — this is called a trailing lambda.
Real-life analogy: Instead of saying “Give me the cake and also put candles on it”, you say: “Give me the cake, and also: put candles on it” → much more natural!
Example – Trailing lambda
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
fun doSomethingWithName(name: String, action: (String) -> Unit) { println("Processing $name...") action(name) } fun main() { // Normal call (ugly) doSomethingWithName("Amit", { name -> println("नमस्ते $name!") }) // Trailing lambda (beautiful!) doSomethingWithName("Priya") { name -> println("नमस्ते $name!") } // Even shorter (single param → 'it') doSomethingWithName("Rahul") { println("नमस्ते $it!") } } |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
Processing Amit... नमस्ते Amit! Processing Priya... नमस्ते Priya! Processing Rahul... नमस्ते Rahul! |
Most famous trailing lambda examples:
|
0 1 2 3 4 5 6 7 8 |
list.forEach { println(it) } // forEach list.map { it.uppercase() } // map thread { println("Running in background") } // Thread constructor |
4. it Implicit Parameter – Single-Parameter Shorthand
When a lambda has exactly one parameter, you can omit the parameter name and use it instead.
Example:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fun main() { val numbers = listOf(1, 2, 3, 4, 5) // Verbose numbers.forEach { number -> println(number) } // Shorter with 'it' numbers.forEach { println(it) } // it = current element // Even with map val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10] } |
When to avoid it:
- When the lambda has multiple lines → better to name the parameter for clarity
- When nested lambdas → it can be confusing
Good style:
|
0 1 2 3 4 5 6 7 8 9 |
list.forEach { item -> println("Processing $item") // more lines... } |
5. Member References (::) – Shorthand for Lambdas
Member references let you refer to a function/property without writing a lambda — like a function pointer.
Syntax: Class::function or instance::function
Types:
| Type | Syntax Example | Equivalent Lambda |
|---|---|---|
| Static / top-level | ::println | { x -> println(x) } |
| Member function | String::uppercase | { str: String -> str.uppercase() } |
| Extension function | String::isBlank | { str: String -> str.isBlank() } |
| Constructor | Person::class or ::Person | { name -> Person(name) } |
| Property reference | Person::name (getter) | { person: Person -> person.name } |
Example – All in action
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
data class Person(val name: String, val age: Int) fun main() { val names = listOf("Amit", "Priya", "Rahul") // Member reference names.forEach(::println) // Prints each name // Map with member reference val upperNames = names.map(String::uppercase) println(upperNames) // [AMIT, PRIYA, RAHUL] // Constructor reference val people = names.map(::Person) // Creates Person("Amit"), etc. println(people) // Property reference val namesList = people.map(Person::name) println(namesList) // [Amit, Priya, Rahul] } |
Very common use: Passing existing functions to map, filter, forEach, etc.
Quick Recap Table (Your Cheat Sheet)
| Feature | Kotlin Way (Best Practice) | Key Benefit |
|---|---|---|
| Lambda | { x -> x * 2 } or { it * 2 } | Anonymous inline function |
| Function type | (Int) -> String | Type of lambda |
| Trailing lambda | list.forEach { println(it) } | Clean, natural syntax |
| it | list.map { it * 2 } | Shorthand for single parameter |
| Member reference | String::uppercase | Shorthand for { str -> str.uppercase() } |
Common Newbie Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Forgetting trailing lambda syntax | forEach({ it -> println(it) }) – ugly | Move lambda outside: forEach { it -> … } |
| Using it in nested lambdas | Confusing which it is which | Name parameters in nested lambdas |
| Not using member references | Verbose code | Use :: when possible (e.g., map(String::uppercase)) |
| Wrong function type | Compile error | Check number of parameters & return type |
| Forgetting parentheses for no params | () -> println(“Hi”) → error | Use empty () for zero parameters |
Homework for You (Let’s Make It Fun!)
- Basic Create a lambda (String) -> String that adds “!!!” to any string → call it with trailing syntax.
- Medium Use listOf(1,2,3,4,5) → map with lambda { it * it } → print squares.
- Advanced Create a higher-order function fun repeat(times: Int, action: () -> Unit) → call with trailing lambda.
- Fun Use member reference String::uppercase to uppercase a list of city names.
- Challenge Fix this code:
Kotlin0123456list.forEach(println(it)) // Wrong!
You’ve just unlocked Kotlin’s lambda superpowers — now you can write concise, expressive, and modern code!
