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
38: List, Set, and Map Operations
When I first started moving from Java to Kotlin, I spent a lot of time writing "Java in Kotlin." I’d create a mutable list, start a for loop, check a condition with an if statement, and manually add the result to my list. It works, sure, but it's noisy. When you're dealing with Lists, Sets, and Maps, Kotlin gives us a functional toolkit that lets us describe what we want to happen, rather than how to move the pointers around.
Let's look at a real scenario: imagine we're building a permission system for an app. We have a list of User objects, and we need to get a list of usernames who have "Admin" privileges, but only if they are currently active.
Manual Iteration vs. Functional Pipelines
The naive way to do this is the way I used to do it—the "accumulator" pattern. You create an empty list, loop through the source, and push items in. It looks like this:
val adminNames = mutableListOf<String>()
for (user in users) {
if (user.role == "Admin" && user.isActive) {
adminNames.add(user.name)
}
}
This isn't "wrong," but it's brittle. You've introduced a mutable variable (adminNames) that exists for the duration of the loop. If this logic grows—say, you need to sort the names or remove duplicates—you end up with a nested mess of loops and temporary variables. I prefer the functional approach using filter and map. It reads like a sentence:
val adminNames = users
.filter { it.role == "Admin" && it.isActive }
.map { it.name }
The trade-off here is a tiny bit of overhead because we're creating intermediate collections under the hood. For a few hundred users, it's irrelevant. If you're processing a million records, I'd tell you to use asSequence() before the filter to make it lazy, but for 95% of your daily work, the pipeline approach is significantly easier to maintain and debug.
The Performance Hit of List-based Lookups
Now, let's talk about uniqueness. Imagine you have a list of all roles assigned to a user, and you need to check if they have a specific "SuperUser" permission. A common mistake I see is keeping these roles in a List and calling .contains().
// The slow way
val roles = listOf("Editor", "Viewer", "Billing", "SuperUser")
if (roles.contains("SuperUser")) {
// Do something
}
If your list is small, who cares, right? But List.contains is an $O(n)$ operation. It has to check every single element until it finds a match. If you're doing this inside another loop, you've just accidentally created a performance bottleneck. If you only care about existence and uniqueness, use a Set. Converting a list to a set via toSet() or defining it as a setOf() changes that lookup to $O(1)$.
// The professional way
val roleSet = roles.toSet()
if (roleSet.contains("SuperUser")) {
// Instant lookup, regardless of set size
}
Mapping IDs to Objects Without the Search Loop
The most frequent "naive" pattern I encounter is the search loop. You have a list of User objects and a list of UserIds you need to look up. I often see developers doing this:
val foundUsers = mutableListOf<User>()
for (id in idsToFind) {
val user = users.find { it.id == id }
if (user != null) foundUsers.add(user)
}
This is a nightmare for performance. You're iterating through the entire users list for every single ID you're looking for. Instead, you should transform your list into a Map first using associateBy. This indexes your data by the key you care about.
val userMap = users.associateBy { it.id }
val foundUsers = idsToFind.mapNotNull { id -> userMap[id] }
By doing this, you've turned a potentially massive nested loop into two linear passes. You spend a little memory to create the userMap, but you save an enormous amount of CPU time. It's a classic space-time trade-off that almost always favors the map in a production environment.
📋 Practical Task
Exercise: Building a User Permission Indexer
You are given a raw list of UserAssignment objects. Each assignment contains a userId, a role (e.g., "Admin", "Editor", "Guest"), and a status ("Active" or "Inactive").
Your goal is to create a function that processes this data and returns a Map<String, Set<String>> where the key is the role and the value is a Set of active userIds belonging to that role.
Requirements:
- Filter out all "Inactive" assignments.
- Ensure the resulting list of userIds for each role contains no duplicates (hence the
Set). - Avoid using manual
forloops and mutable lists; use Kotlin's collection operators likefilter,groupBy, andmapValues.
data class UserAssignment(val userId: String, val role: String, val status: String)
fun indexActiveUsersByRole(assignments: List<UserAssignment>): Map<String, Set<String>> {
// Your implementation here
}
// Test your code with this:
val data = listOf(
UserAssignment("u1", "Admin", "Active"),
UserAssignment("u2", "Editor", "Active"),
UserAssignment("u1", "Admin", "Active"), // Duplicate
UserAssignment("u3", "Admin", "Inactive"), // Should be filtered
UserAssignment("u4", "Editor", "Active")
)
// Expected Result: {"Admin"=[u1], "Editor"=[u2, u4]}
There are no comments for now.