← All courses

Collections & Functional Operations

🗓 May 31, 2026 ⏱ 2 min read

The three collection types

  • List — an ordered series of items (can repeat).
  • Set — a collection of unique items (no duplicates).
  • Map — key → value pairs (like a dictionary).

Each comes in a read-only and a mutable form. Prefer read-only unless you need to change the contents.

val list = listOf(1, 2, 3)               // read-only
val mutable = mutableListOf(1, 2)        // can add/remove
mutable.add(3)

val set = setOf(1, 1, 2, 3)              // -> [1, 2, 3]
val map = mapOf("a" to 1, "b" to 2)
println(map["a"])                        // 1

Why functional operations beat loops

Instead of writing for loops with temporary variables, Kotlin gives you readable operations that describe what you want. They’re shorter and less error-prone.

val nums = listOf(1, 2, 3, 4, 5, 6)

nums.filter { it % 2 == 0 }      // keep evens -> [2, 4, 6]
nums.map { it * it }             // transform   -> [1, 4, 9, 16, 25, 36]
nums.first { it > 3 }            // first match -> 4
nums.find { it > 10 }            // or null     -> null
nums.any { it > 5 }              // is one true -> true
nums.all { it > 0 }             // all true    -> true
nums.count { it % 2 == 0 }       // how many    -> 3
nums.sumOf { it }                // total       -> 21
nums.maxOrNull()                 // 6
nums.sorted()                    // [1,2,3,4,5,6]
nums.groupBy { it % 2 }          // {1=[1,3,5], 0=[2,4,6]}
nums.associateWith { it * it }   // {1=1, 2=4, ...}

Chaining operations

The real power comes from chaining — each step feeds the next, reading top to bottom like a sentence.

data class User(val name: String, val age: Int, val city: String)

val users = listOf(
    User("Anand", 28, "Delhi"),
    User("Priya", 34, "Mumbai"),
    User("Ravi", 22, "Delhi")
)

val delhiNamesSorted = users
    .filter { it.city == "Delhi" }   // only Delhi
    .sortedBy { it.age }             // youngest first
    .map { it.name }                 // just the names
// -> [Ravi, Anand]

Maps in practice

val counts = mutableMapOf<String, Int>()
for (word in listOf("a", "b", "a")) {
    counts[word] = (counts[word] ?: 0) + 1
}
// {a=2, b=1}

// the functional way
val counts2 = listOf("a", "b", "a").groupingBy { it }.eachCount()

Common mistakes

  • Using a mutable list when a read-only one would do (mutability invites bugs).
  • Chaining filter then map over a huge list repeatedly — for very large data, use asSequence() to process lazily.
  • Reaching into a map with map[key]!! — handle the missing-key (null) case.
Summary: Pick List/Set/Map by need, prefer read-only, and replace manual loops with filter/map/groupBy chains for clear, safe data transformations.