Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
25: Lambda Expressions and Higher-Order Functions
I've seen a lot of developers hit a wall when they start building business logic that requires a bit of flexibility. Usually, it happens when you're writing something like a data filtering system. You start with one requirement, then another, and suddenly your class is bloated with a dozen nearly identical methods. Let's look at how that usually happens and how we can fix it using lambdas and higher-order functions.
The Method Bloat Trap
Imagine we're building a simple financial tracker. We have a Transaction data class, and we need to filter our list of transactions based on different criteria. The intuitive, "naive" approach is to write a specific function for every single filter the product owner asks for.
data class Transaction(val description: String, val amount: Double, val category: String)
class TransactionManager {
fun filterByCategory(list: List, category: String): List {
return list.filter { it.category == category }
}
fun filterByMinAmount(list: List, min: Double): List {
return list.filter { it.amount >= min }
}
fun filterByDescription(list: List, query: String): List {
return list.filter { it.description.contains(query, ignoreCase = true) }
}
}
At first, this feels clean. But here is the problem: logic is leaking. Every time you need a new filter—say, transactions from a specific date range or those marked as "pending"—you have to modify the TransactionManager class, add a new method, and recompile. You're essentially hard-coding every possible business rule into the core of your manager. It's brittle, and frankly, it's boring to write.
Passing Logic as Data
The "pro" move here is to realize that the only thing changing between those three functions is the condition used to decide if a transaction stays or goes. In Kotlin, we can treat that condition as a first-class citizen. We can pass a function as an argument to another function. This is what we call a Higher-Order Function.
Instead of writing five different filter methods, we write one that accepts a lambda expression—a small, anonymous block of code that takes a Transaction and returns a Boolean.
class TransactionManager {
// This is the Higher-Order Function.
// The 'predicate' parameter is actually a function: (Transaction) -> Boolean
fun filterTransactions(list: List, predicate: (Transaction) -> Boolean): List {
return list.filter(predicate)
}
}
// Now, look how we use it:
val manager = TransactionManager()
val txs = listOf(Transaction("Coffee", 4.50, "Food"), Transaction("Rent", 1200.0, "Housing"))
// We define the logic on the fly using { }
val expensiveOnes = manager.filterTransactions(txs) { it.amount > 100.0 }
val foodOnly = manager.filterTransactions(txs) { it.category == "Food" }
val coffeeSearch = manager.filterTransactions(txs) { it.description.contains("Coffee") }
I love this approach because TransactionManager no longer cares how you filter; it only cares that you've provided a rule to do so. You've decoupled the mechanism of filtering from the criteria of the filter.
The Cost of Flexibility
Now, I wouldn't be doing my job if I didn't mention the trade-offs. Lambdas aren't a free lunch. In a very tight loop—like processing millions of transactions per second—creating these function objects can introduce a slight overhead. If you're seeing a performance hit, Kotlin provides the inline keyword. By marking your higher-order function as inline, the compiler effectively copies the lambda code directly into the call site, erasing the object overhead.
But for 95% of your app's code, don't over-optimize. The mental clarity of having one flexible function instead of twenty rigid ones is worth far more than a few nanoseconds of CPU time. You're trading a tiny bit of memory for a massive gain in maintainability.
📋 Practical Task
Build a Flexible Permission Guard
You are building a security module for an admin dashboard. Instead of writing separate checks for isAdmin(), isEditor(), and isOwner(), you need to implement a single PermissionGuard class that can handle any custom permission logic passed to it.
Your task:
- Create a
Userdata class with properties forusername,role, andaccessLevel(Int). - Implement a
PermissionGuardclass with a higher-order function calledcheckAccess. This function should take aUserobject and a predicate lambda(User) -> Boolean. It should return aString: "Access Granted" if the lambda returns true, and "Access Denied" otherwise. - In your
mainfunction, use this guard to test three different scenarios using lambdas:- Grant access if the user is an "Admin".
- Grant access if the user's
accessLevelis greater than 5. - Grant access if the username is "SuperUser".
There are no comments for now.