Collections
Collection Hierarchy
Section titled “Collection Hierarchy”Kotlin’s collection types are split into two hierarchies: read-only and mutable.
Collection (read-only) |-- List |-- Set |-- MapMutableCollection extends Collection |-- MutableList |-- MutableSet |-- MutableMapRead-only interfaces do not guarantee immutability — they expose no mutation methods. The Underlying collection may still be mutable through a different reference.
// Read-onlyval readOnly: List<Int> = listOf(1, 2, 3)
// Mutableval mutable: MutableList<Int> = mutableListOf(1, 2, 3)mutable.add(4)mutable[0] = 10List is an ordered collection with index-based access. listOf() returns an immutable list Implementation. mutableListOf() returns a MutableList.
List Operations
Section titled “List Operations”val list = listOf(3, 1, 4, 1, 5, 9)
list[0] // 3list.indexOf(1) // 1 (first occurrence)list.lastIndexOf(1) // 3list.subList(1, 4) // [1, 4, 1]list.reversed() // [9, 5, 1, 4, 1, 3]list.sorted() // [1, 1, 3, 4, 5, 9]list.distinct() // [3, 1, 4, 5, 9]list.contains(4) // trueval readOnly: Set<Int> = setOf(1, 2, 3, 2, 1) // {1, 2, 3}val mutable: MutableSet<Int> = mutableSetOf(1, 2, 3)
val linked: LinkedHashSet<Int> = linkedSetOf(3, 1, 2) // preserves insertion orderval hashed: HashSet<Int> = hashSetOf(3, 1, 2) // no order guaranteeval sorted: TreeSet<Int> = sortedSetOf(3, 1, 2) // natural orderingSet Operations
Section titled “Set Operations”val a = setOf(1, 2, 3, 4)val b = setOf(3, 4, 5, 6)
a union b // {1, 2, 3, 4, 5, 6}a intersect b // {3, 4}a subtract b // {1, 2}a - b // {1, 2}val readOnly: Map<String, Int> = mapOf("a" to 1, "b" to 2, "c" to 3)val mutable: MutableMap<String, Int> = mutableMapOf("a" to 1, "b" to 2)Map Operations
Section titled “Map Operations”val map = mapOf("x" to 1, "y" to 2, "z" to 3)
map["x"] // 1map.getOrDefault("w", 0) // 0map.getOrElse("w") { 42 } // 42map.keys // Set<String> = {x, y, z}map.values // Collection<Int> = [1, 2, 3]map.filterKeys { it != "z" } // {x=1, y=2}map.filterValues { it > 1 } // {y=2, z=3}map.mapKeys { it.key.uppercase() } // {X=1, Y=2, Z=3}map.mapValues { it.value * 2 } // {x=2, y=4, z=6}Map Access Patterns
Section titled “Map Access Patterns”val scores = mutableMapOf("Alice" to 95, "Bob" to 82)
scores.computeIfAbsent("Charlie") { 70 } // inserts if absent, returns valuescores.computeIfPresent("Alice") { _, v -> if (v > 90) v + 5 else v } // updates if present
scores.getOrPut("David") { 60 } // returns existing or inserts and returnsTransformation Operations
Section titled “Transformation Operations”Applies a transformation to each element and returns a new collection.
val names = listOf("alice", "bob", "charlie")val uppercased = names.map { it.uppercase() } // [ALICE, BOB, CHARLIE]
val users = listOf(User("Alice", 30), User("Bob", 25))val namesAndAges = users.map { "${it.name} (${it.age})" }mapIndexed provides the index alongside the element:
val indexed = names.mapIndexed { index, name -> "$index: $name" }// [0: alice, 1: bob, 2: charlie]mapNotNull filters out null results:
val parsed = listOf("1", "two", "3", "four").mapNotNull { it.toIntOrNull() }// [1, 3]filter
Section titled “filter”Returns elements matching the predicate.
val even = listOf(1, 2, 3, 4, 5).filter { it % 2 == 0 } // [2, 4]val nonEmpty = listOf("", "a", "", "bc").filterNot { it.isEmpty() } // [a, bc]
listOf(1, 2, 3, 4, 5).filterIndexed { index, _ -> index % 2 == 0 } // [1, 3, 5]flatMap
Section titled “flatMap”Maps each element to a collection, then flattens the result.
val sentences = listOf("hello world", "kotlin language")val words = sentences.flatMap { it.split(" ") } // [hello, world, kotlin, language]fold and reduce
Section titled “fold and reduce”fold takes an initial accumulator value. reduce uses the first element as the initial value.
val sum = listOf(1, 2, 3, 4, 5).fold(0) { acc, n -> acc + n } // 15val product = listOf(1, 2, 3, 4, 5).reduce { acc, n -> acc * n } // 120
val result = listOf("a", "b", "c").fold(StringBuilder()) { sb, s -> sb.append(s)}.toString() // "abc"reduce throws NoSuchElementException on empty collections. Use reduceOrNull for safe handling.
val empty: Int? = emptyList<Int>().reduceOrNull { a, b -> a + b } // nullgroupBy
Section titled “groupBy”Groups elements by a key and returns a Map<K, List<V>>.
data class Person(val name: String, val city: String, val age: Int)
val people = listOf( Person("Alice", "NYC", 30), Person("Bob", "NYC", 25), Person("Charlie", "LA", 35))
val byCity = people.groupBy { it.city }// {NYC=[Person(Alice, NYC, 30), Person(Bob, NYC, 25)], LA=[Person(Charlie, LA, 35)]}
val avgAgeByCity = people.groupBy( keySelector = { it.city }, valueTransform = { it.age }).mapValues { (_, ages) -> ages.average() }// {NYC=27.5, LA=35.0}partition
Section titled “partition”Splits a collection into two lists based on a predicate.
val (pass, fail) = listOf(85, 42, 91, 67, 55, 98).partition { it >= 60 }// pass = [85, 91, 67, 98], fail = [42, 55]Pairs elements from two collections.
val keys = listOf("a", "b", "c")val values = listOf(1, 2, 3)val pairs = keys zip values // [(a, 1), (b, 2), (c, 3)]val map = keys.zip(values).toMap() // {a=1, b=2, c=3}chunked and windowed
Section titled “chunked and windowed”val data = listOf(1, 2, 3, 4, 5, 6, 7, 8)data.chunked(3) // [[1, 2, 3], [4, 5, 6], [7, 8]]data.windowed(3) // [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8]]data.windowed(3, 3) // [[1, 2, 3], [4, 5, 6], [7, 8]] -- step size 3associate
Section titled “associate”val users = listOf(User("Alice", 30), User("Bob", 25))val byName = users.associate { it.name to it }val byNameV2 = users.associateBy { it.name }val byNameV3 = users.associateBy(keySelector = { it.name }, valueTransform = { it.age })// byNameV3 = {Alice=30, Bob=25}Sequence
Section titled “Sequence”Sequences are lazy — transformations are not executed until a terminal operation is invoked. For Large collections or chains of operations, sequences avoid creating intermediate collections.
val result = (1..1_000_000) .asSequence() .filter { it % 2 == 0 } .map { it * it } .take(5) .toList()// [4, 16, 36, 64, 100]With a list, each intermediate operation creates a new list:
list.filter { ... }.map { ... }.take(5)// Creates: filtered list -> mapped list -> then takes 5With a sequence, each element flows through the entire pipeline before the next element is Processed:
list.asSequence().filter { ... }.map { ... }.take(5)// Processes: element 1 (filter -> map), element 2, ... until 5 collectedWhen to Use Sequences
Section titled “When to Use Sequences”- ** Use sequences when the collection is large and you have multiple chained operations.
- ** Use sequences when you need only a subset of the result (e.g.,
first,take). - ** Use lists when the collection is small or you need to transform the entire collection.
Sequence Creation
Section titled “Sequence Creation”val seq1 = listOf(1, 2, 3).asSequence()val seq2 = sequenceOf(1, 2, 3)val seq3 = generateSequence(1) { it * 2 } // 1, 2, 4, 8, 16, ... (lazy, infinite)val seq4 = generateSequence(seed = 0) { if (it < 100) it + 1 else null } // 0..99Common Pitfalls
Section titled “Common Pitfalls”- ** Chaining multiple operations on large lists without using sequences. This creates intermediate collections at each step, increasing memory pressure and GC overhead.
- ** Using
associateBywhen keys are not unique. Only the last value for each key is retained. UsegroupBywhen you need to keep all values. - ** Modifying a mutable collection while iterating over it. This throws
ConcurrentModificationException. UsetoList()to create a snapshot orremoveIffor conditional removal. - ** Assuming read-only collections are immutable.
List<Int>is a read-only interface; the underlying implementation may be mutable. UsetoList()ortoImmutableList()(Kotlinx Collections) for defensive copies.
Summary
Section titled “Summary”This topic covers the core concepts of collections, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Worked Examples
Section titled “Worked Examples”Intuition
Section titled “Intuition”Kotlin collections split into read-only and mutable interfaces, where read-only means no mutation methods are exposed — but the underlying object may still be mutable through another reference. Transformation operations like map, filter, and flatMap create new collections, enabling a functional style without modifying originals. Sequences are lazy versions of collections that process elements one at a time through the entire pipeline, avoiding intermediate collection allocation for large datasets. Grouping, partitioning, and association operations reorganize collections by keys or predicates. The toList() function creates a defensive snapshot, which is essential when modifying a collection during iteration.
Cross-References
Section titled “Cross-References”- Types and Variables — nullable collections vs collections of nullables
- Functions — higher-order functions like map and filter
- Coroutines — Flow as a lazy asynchronous collection