Collections
Learn Kotlin's List, Set, and Map types, mutable vs read-only collections, and the standard library's functional operators like map, filter, and reduce.
Introduction
Kotlin's collection types build directly on top of Java's existing collection framework — a `List<String>` in Kotlin is a real `java.util.List` underneath. What Kotlin adds is a crucial distinction Java lacks: separate read-only and mutable interfaces for every collection type, plus a rich set of functional operators for transforming data without writing manual loops.
This lesson covers List, Set, and Map, the read-only/mutable distinction, and the functional-style operators — `map`, `filter`, `forEach`, `reduce`, and sorting — that make working with collections in Kotlin dramatically more concise than in Java.
Read-Only vs Mutable Collections
Every collection type in Kotlin has two interfaces: a read-only one (`List`, `Set`, `Map`) that exposes no way to add, remove, or change elements, and a mutable one (`MutableList`, `MutableSet`, `MutableMap`) that does. This is a design-level safety feature, similar in spirit to `val` vs `var` — it lets you expose a collection to other code with a guarantee it cannot be modified.
fun main() { val readOnly: List<String> = listOf("Kotlin", "Java") val mutable: MutableList<String> = mutableListOf("Kotlin", "Java")
// readOnly.add("Swift") // Compile error: List has no add() method mutable.add("Swift") // OK
println(readOnly) println(mutable)}Click Run to see what this code prints.
A read-only `List` is not necessarily immutable — it might be backed by a `MutableList` elsewhere that another part of the code changes. `List` only guarantees that this particular reference has no mutating methods; it does not guarantee the underlying data never changes.
List
A `List` is an ordered collection that allows duplicate elements, accessed by index. `listOf()` creates a read-only list; `mutableListOf()` creates a mutable one.
fun main() { val fruits = listOf("Apple", "Banana", "Apple", "Cherry")
println(fruits[0]) // indexed access println(fruits.size) println(fruits.contains("Banana"))}Click Run to see what this code prints.
Set
A `Set` is an unordered collection that automatically discards duplicate elements. `setOf()` creates a read-only set; `mutableSetOf()` creates a mutable one.
fun main() { val uniqueNumbers = setOf(1, 2, 2, 3, 3, 3) println(uniqueNumbers) // duplicates removed automatically println(uniqueNumbers.size)}Click Run to see what this code prints.
Map
A `Map` stores key-value pairs with unique keys. `mapOf()` creates a read-only map; `mutableMapOf()` creates a mutable one. Kotlin's `to` infix function builds a `Pair`, which is the idiomatic way to write map entries.
fun main() { val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"]) // 30 println(ages["Unknown"]) // null — no exception println(ages.getOrDefault("Unknown", 0)) // 0
for ((name, age) in ages) { println("$name is $age") }}Click Run to see what this code prints.
Functional Operators: map, filter, forEach
Kotlin's standard library adds a large set of functional-style extension functions to every collection, letting you transform data declaratively instead of writing manual loops. `map` transforms each element, `filter` keeps only elements matching a condition, and `forEach` runs an action on each element.
fun main() { val numbers = listOf(1, 2, 3, 4, 5, 6)
val doubled = numbers.map { it * 2 } val evens = numbers.filter { it % 2 == 0 }
println(doubled) println(evens)
evens.forEach { println("Even: $it") }}Click Run to see what this code prints.
These operators can be chained fluently, since each one returns a new collection rather than mutating the original — `numbers.filter { it % 2 == 0 }.map { it * it }` reads almost like a sentence: "filter the numbers to evens, then square each one."
reduce and fold
`reduce` combines all elements of a collection into a single value by repeatedly applying an operation, using the first element as the starting accumulator. `fold` works the same way but lets you supply your own starting value, which also makes it safe to use on empty collections.
fun main() { val numbers = listOf(1, 2, 3, 4)
val sum = numbers.reduce { acc, n -> acc + n } val sumWithBase = numbers.fold(100) { acc, n -> acc + n }
println(sum) println(sumWithBase)}Click Run to see what this code prints.
Sorting Collections
`sorted()` sorts elements using their natural order; `sortedBy` and `sortedByDescending` sort using a selector you provide, which is especially useful for sorting objects by a specific property.
data class Employee(val name: String, val salary: Int)
fun main() { val employees = listOf( Employee("Nina", 75000), Employee("Omar", 62000), Employee("Priya", 91000) )
val bySalary = employees.sortedByDescending { it.salary } bySalary.forEach { println("${it.name}: ${it.salary}") }}Click Run to see what this code prints.
Common Mistakes
- Assuming `List` means immutable — it only means "no mutating methods on this reference," not that the underlying data can never change.
- Using `mutableListOf()` by default out of habit — prefer read-only `listOf()` unless you specifically need to add/remove elements.
- Chaining many `map`/`filter` calls on very large collections without considering `asSequence()`, which processes elements lazily instead of building an intermediate list at every step.
- Forgetting that map access with `[]` returns null for a missing key instead of throwing, unlike some other languages' map/dictionary types.
Best Practices
- Default to read-only collection types (`List`, `Set`, `Map`) for anything you expose outside a function or class.
- Prefer `map`/`filter`/`reduce`-style operators over manual `for` loops with an accumulator — they are more concise and less error-prone.
- Use `getOrDefault` or the Elvis operator (`map[key] ?: default`) instead of assuming a map lookup always succeeds.
- Reach for `sortedBy { it.property }` instead of writing a manual `Comparator` for simple property-based sorting.
Frequently Asked Questions
Under the hood, yes — Kotlin's List, Set, and Map are backed by the same java.util collection classes. Kotlin adds the read-only/mutable interface split and a large library of extension functions on top.
`map` transforms each element and returns a new collection of the results; `forEach` just performs an action on each element (like printing) and returns nothing useful (Unit).
Use a Set when you need automatic duplicate removal and don't care about element order or index-based access; use a List when order and duplicates matter, or when you need to access elements by position.
Key Takeaways
- Kotlin splits every collection into a read-only interface (List, Set, Map) and a mutable one (MutableList, MutableSet, MutableMap).
- List preserves order and allows duplicates; Set automatically removes duplicates; Map stores unique keys mapped to values.
- Functional operators like `map`, `filter`, and `forEach` replace most manual loops with concise, chainable expressions.
- `reduce` and `fold` combine a collection into a single value; `fold` additionally accepts a starting value.
- `sortedBy`/`sortedByDescending` sort collections using a selector, without writing a manual Comparator.
Summary
You can now store, transform, and query data confidently using Kotlin's List, Set, and Map types and its functional-style operators. Next, you'll learn two of Kotlin's most distinctive class types: data classes and sealed classes.