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.
Intuition
Section titled “Intuition”Collections are containers for groups of related data. Lists store ordered sequences, sets enforce uniqueness, and maps store key-value pairs. Kotlin’s collection hierarchy separates read-only from mutable interfaces, making it clear at the type level whether a collection can be modified. Transformation operations like map, filter, and fold let you express data pipelines declaratively, treating collections as streams to be shaped rather than loops to be managed.
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
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- Coroutines — Flow operators on collections parallel coroutine-based async processing; both use functional transformation patterns.
- Generics — Collection type parameters and variance annotations (in/out) are governed by the generic type system.
- Delegation and Result — Property delegation can be used to lazily initialise collections or wrap them with observable behaviour.
- Coroutines Advanced — StateFlow and SharedFlow build on collection concepts to provide reactive state management.