iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Collections

Kotlin’s collections are powerful: immutable by default, with rich functional operations (map, filter, groupBy, fold) and sequences for lazy chains. Knowing the mutable vs read-only split and when to use sequences keeps code idiomatic and fast.

List, Map, Set, sequences, operations

EXAMPLE
// 1) Read-only vs mutable
val ro: List<Int> = listOf(1, 2, 3)            // read-only
val mut: MutableList<Int> = mutableListOf(1, 2, 3)
mut.add(4)
// ro.add(4)        // compile error

val roMap: Map<String, Int> = mapOf("a" to 1, "b" to 2)
val mutMap = mutableMapOf("a" to 1).apply { this["b"] = 2 }

val roSet: Set<Int> = setOf(1, 2, 3)
val mutSet = mutableSetOf(1, 2, 3)

// 2) Construction
val primes  = listOf(2, 3, 5, 7, 11)
val nullable = listOfNotNull(1, null, 2)        // [1, 2]
val range    = (1..10).toList()
val squares  = List(5) { it * it }              // [0, 1, 4, 9, 16]
val emptyL   = emptyList<Int>()
val str2int  = mapOf("a" to 1, "b" to 2)
val byPair   = listOf("a" to 1, "b" to 2).toMap()

// 3) Core ops — map / filter / fold / flatMap
listOf(1, 2, 3, 4, 5).map { it * 2 }                 // [2, 4, 6, 8, 10]
listOf(1, 2, 3, 4, 5).filter { it % 2 == 0 }         // [2, 4]
listOf(1, 2, 3, 4, 5).filterNot { it > 3 }            // [1, 2, 3]
listOf(1, 2, 3).fold(0) { acc, x -> acc + x }         // 6
listOf(1, 2, 3).reduce { acc, x -> acc + x }          // 6
listOf(listOf(1, 2), listOf(3)).flatten()             // [1, 2, 3]
listOf("ab", "cd").flatMap { it.toList() }            // ['a','b','c','d']

// 4) Group + partition + chunk + window
val words = listOf("apple", "banana", "cherry", "date")
words.groupBy { it.length }            // {5=[apple], 6=[banana,cherry], 4=[date]}
words.partition { it.length > 4 }      // ([apple,banana,cherry], [date])
words.associate { it to it.length }    // {apple=5, banana=6, ...}
words.associateBy { it.first() }       // {a=apple, b=banana, c=cherry, d=date}

listOf(1, 2, 3, 4, 5).chunked(2)        // [[1,2], [3,4], [5]]
listOf(1, 2, 3, 4, 5).windowed(3)       // [[1,2,3], [2,3,4], [3,4,5]]
listOf(1, 2, 3, 4, 5).windowed(2, step = 2, partialWindows = true)  // [[1,2],[3,4],[5]]

// 5) Lookups
listOf(1, 2, 3).first()                                  // 1; throws on empty
listOf(1, 2, 3).firstOrNull { it > 2 }                  // 3
listOf(1, 2, 3).find { it > 5 }                          // null
listOf(1, 2, 3).single { it == 2 }                       // 2; throws if 0 or >1 matches
listOf(1, 2, 3).any { it > 2 }                           // true
listOf(1, 2, 3).all { it > 0 }                            // true
listOf(1, 2, 3).count { it > 1 }                          // 2
listOf(2, 1, 3).maxOrNull()                               // 3
listOf(2, 1, 3).maxByOrNull { it % 2 }                    // 3 (largest by 'it % 2')

// 6) Sorting
listOf(3, 1, 2).sorted()                                  // [1,2,3]
listOf(3, 1, 2).sortedDescending()                        // [3,2,1]
listOf("banana", "apple").sortedBy { it.length }          // [apple, banana]
listOf("banana", "apple").sortedWith(compareBy({ it.length }, { it }))

// 7) Slicing
listOf(1, 2, 3, 4, 5).take(3)                              // [1,2,3]
listOf(1, 2, 3, 4, 5).takeWhile { it < 3 }                  // [1,2]
listOf(1, 2, 3, 4, 5).drop(2)                              // [3,4,5]
listOf(1, 2, 3, 4, 5).dropLastWhile { it > 3 }              // [1,2,3]
listOf(1, 2, 3, 4, 5).slice(1..3)                          // [2,3,4]
listOf(1, 2, 3, 4, 5).slice(listOf(0, 2, 4))                // [1,3,5]

// 8) Combining
listOf(1, 2, 3) + listOf(4, 5)                              // [1,2,3,4,5]
listOf(1, 2, 3).plus(4)                                       // [1,2,3,4]
listOf(1, 2, 3).minus(2)                                      // [1,3]
listOf(1, 2, 3).zip(listOf("a", "b", "c"))                  // [(1,a),(2,b),(3,c)]
listOf(1, 2, 3).zipWithNext()                                // [(1,2),(2,3)]

// 9) Maps — manipulating
val scores = mapOf("Mara" to 90, "Sam" to 75, "Alex" to 88)
scores.entries.sortedByDescending { it.value }.take(2)
scores.filter { (_, v) -> v >= 80 }                          // {Mara=90, Alex=88}
scores.mapValues { (_, v) -> v + 5 }                          // bump
scores.mapKeys { (k, _) -> k.uppercase() }
scores.toList().sortedByDescending { it.second }.toMap()

// 10) Sequences — lazy chains, evaluated on demand
val result = (1..1_000_000)
    .asSequence()                          // -> Sequence<Int>
    .map { it * 2 }
    .filter { it % 3 == 0 }
    .take(5)
    .toList()
// Without asSequence(): each intermediate would allocate a million-element list.
// With asSequence(): processes one element at a time through the chain.

// Rule of thumb: sequences win for LONG chains + LARGE inputs. For small / short pipelines, eager collections are simpler.

// 11) Mutable collection ops
val ml = mutableListOf(1, 2, 3, 4)
ml.removeAll { it % 2 == 0 }            // [1, 3]
ml.replaceAll { it * 10 }                // [10, 30]
ml.sort()
ml.shuffle()
ml.clear()

val mm = mutableMapOf("a" to 1)
mm.getOrPut("b") { 2 }                   // inserts if absent
mm.compute("a") { _, v -> (v ?: 0) + 1 } // upsert
mm.merge("b", 5) { old, add -> old + add }  // 'b' -> 7

// 12) Defensive copies
fun render(items: List<String>) {
    // Treat as immutable; defensively copy when STORING:
    val mine = items.toList()              // shallow copy
    /* store mine, use it later */
}

// 13) Collections vs Arrays vs IntArray
// • List<Int> — boxed Integers, generic, flexible API
// • IntArray — unboxed primitives, smaller + faster for numeric hot paths
// • Array<Int> — boxed; use IntArray instead for performance

val primes = intArrayOf(2, 3, 5, 7)        // unboxed
val doubled = IntArray(primes.size) { primes[it] * 2 }

// 14) Java interop
// • Kotlin's read-only types are Java's MUTABLE types under the hood
//   (List<Int> = java.util.List<Integer>). Don't mutate from Java — convention.
// • For TRUE immutability, copy: List.copyOf(...) from Java 10+.

// 15) Common bugs
// • Treating List<T> as immutable in Java code → it isn't; toList() / Collections.unmodifiableList for safety
// • Re-running an eager pipeline by calling toList() multiple times — operations re-execute each call; cache the result
// • Forgetting asSequence() on a million-row map.filter.map chain → big GC pressure
// • Using listOf() expecting a MutableList — different type; can't add()
// • Mixing in/out variance with mutable collections — write requires invariance
// • Maps' insertion order — LinkedHashMap is default; HashMap (mapOf) has predictable order in JVM 8+
// • removeAll vs retainAll mix-ups — opposite semantics; choose carefully

Why it matters

Lean on Kotlin’s read-only types by default, reach for mutable when needed, and use sequences (asSequence) for long pipelines over big inputs so intermediate allocations don’t balloon. The operator vocabulary (groupBy, partition, chunked, fold, flatMap) lets most data transforms read as one expressive chain.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
val xs = listOf(1, 2, 3)
val m = mapOf("ada" to 36, "bo" to 28)
val s = setOf("red", "blue")
println(xs.map { it * it }.sum())
Try it Yourself »

Discussion

Loading…