Skip to Content
Course content

38: List, Set, and Map Operations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 for loops and mutable lists; use Kotlin's collection operators like filter, groupBy, and mapValues.
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]}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.