In the previous tutorial, you learned about classes. Now let’s learn about collections — one of the most important topics in Kotlin. You will use collections in almost every program you write.
Kotlin’s collections are powerful and expressive. You can filter, transform, group, and combine data with just a few lines of code.
In this tutorial, you will learn:
- List, MutableList, Set, and Map
- filter, map, flatMap
- groupBy, sortedBy
- reduce and fold
- zip and associate
- Chaining operations together
List
A List is an ordered collection. listOf creates a read-only list. mutableListOf creates a list you can change.
Read-Only List
val fruits = listOf("Apple", "Banana", "Cherry", "Date")
println(fruits.size) // 4
println(fruits.first()) // Apple
println(fruits.last()) // Date
println(fruits[2]) // Cherry
println(fruits.contains("Apple")) // true
println(fruits.indexOf("Cherry")) // 2
println(fruits.isEmpty()) // false
You cannot add, remove, or change elements in a read-only list.
MutableList
val fruits = mutableListOf("Apple", "Banana")
fruits.add("Cherry")
fruits.add(0, "Avocado") // Add at index 0
fruits.remove("Banana")
println(fruits) // [Avocado, Apple, Cherry]
Converting Between Types
val readOnly = fruits.toList() // MutableList -> List
val mutable = readOnly.toMutableList() // List -> MutableList
Set
A Set is a collection with no duplicate elements:
val colors = setOf("Red", "Green", "Blue", "Red", "Green")
println(colors) // [Red, Green, Blue]
println(colors.size) // 3 (duplicates removed)
Set Operations
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
println(set1.union(set2)) // [1, 2, 3, 4, 5, 6]
println(set1.intersect(set2)) // [3, 4]
println(set1.subtract(set2)) // [1, 2]
MutableSet
val colors = mutableSetOf("Red", "Green")
colors.add("Blue")
colors.add("Red") // No effect — already exists
println(colors) // [Red, Green, Blue]
Map
A Map stores key-value pairs. Keys are unique:
val scores = mapOf("Alex" to 95, "Sam" to 87, "Jordan" to 92)
println(scores["Alex"]) // 95
println(scores.keys) // [Alex, Sam, Jordan]
println(scores.values) // [95, 87, 92]
println(scores.containsKey("Sam")) // true
println(scores.getOrDefault("Taylor", 0)) // 0
Iterate Over a Map
for ((name, score) in scores) {
println("$name: $score")
}
MutableMap
val scores = mutableMapOf("Alex" to 95)
scores["Sam"] = 87
scores["Jordan"] = 92
scores.remove("Alex")
println(scores) // {Sam=87, Jordan=92}
filter
filter keeps only elements that match a condition:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val evens = numbers.filter { it % 2 == 0 }
println(evens) // [2, 4, 6, 8, 10]
val odds = numbers.filterNot { it % 2 == 0 }
println(odds) // [1, 3, 5, 7, 9]
val greaterThan5 = numbers.filter { it > 5 }
println(greaterThan5) // [6, 7, 8, 9, 10]
Filter on maps:
val scores = mapOf("Alex" to 95, "Sam" to 65, "Jordan" to 87)
val passing = scores.filter { (_, score) -> score >= 70 }
println(passing) // {Alex=95, Jordan=87}
filterIndexed
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val everyThird = numbers.filterIndexed { index, _ -> index % 3 == 0 }
println(everyThird) // [1, 4, 7, 10]
map (Transform)
map transforms each element and returns a new collection:
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println(doubled) // [2, 4, 6, 8, 10]
val squared = numbers.map { it * it }
println(squared) // [1, 4, 9, 16, 25]
Transform strings:
val names = listOf("alex", "sam", "jordan")
val capitalized = names.map { it.replaceFirstChar { c -> c.uppercase() } }
println(capitalized) // [Alex, Sam, Jordan]
mapIndexed
val names = listOf("alex", "sam", "jordan")
val indexed = names.mapIndexed { index, name -> "${index + 1}. $name" }
println(indexed) // [1. alex, 2. sam, 3. jordan]
mapNotNull
Transform and skip null results:
val strings = listOf("1", "2", "hello", "4", "world")
val validNumbers = strings.mapNotNull { it.toIntOrNull() }
println(validNumbers) // [1, 2, 4]
flatMap
flatMap transforms each element to a collection, then flattens everything into one list:
data class Order(val id: Int, val items: List<String>, val total: Double)
val orders = listOf(
Order(1, listOf("Laptop", "Mouse"), 1200.0),
Order(2, listOf("Keyboard", "Monitor", "Headphones"), 800.0),
Order(3, listOf("Phone"), 500.0)
)
val allItems = orders.flatMap { it.items }
println(allItems) // [Laptop, Mouse, Keyboard, Monitor, Headphones, Phone]
For simple nested lists, use flatten:
val nested = listOf(listOf(1, 2), listOf(3, 4), listOf(5))
val flat = nested.flatten()
println(flat) // [1, 2, 3, 4, 5]
groupBy
groupBy groups elements by a key:
data class Student(val name: String, val grade: Int, val score: Double)
val students = listOf(
Student("Alex", 10, 95.0),
Student("Sam", 10, 87.0),
Student("Jordan", 11, 92.0),
Student("Taylor", 11, 78.0),
Student("Casey", 10, 83.0)
)
val byGrade = students.groupBy { it.grade }
for ((grade, group) in byGrade) {
println("Grade $grade: ${group.map { it.name }}")
}
// Grade 10: [Alex, Sam, Casey]
// Grade 11: [Jordan, Taylor]
Group numbers by even/odd:
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evenOdd = numbers.groupBy { if (it % 2 == 0) "Even" else "Odd" }
println(evenOdd) // {Odd=[1, 3, 5], Even=[2, 4, 6]}
Sorting
val numbers = listOf(5, 3, 8, 1, 9, 2, 7)
println(numbers.sorted()) // [1, 2, 3, 5, 7, 8, 9]
println(numbers.sortedDescending()) // [9, 8, 7, 5, 3, 2, 1]
Sort by a property:
val students = listOf(
Student("Alex", 10, 95.0),
Student("Sam", 10, 87.0),
Student("Jordan", 11, 92.0)
)
val byScore = students.sortedBy { it.score }
println(byScore.map { it.name }) // [Sam, Jordan, Alex]
val byScoreDesc = students.sortedByDescending { it.score }
println(byScoreDesc.map { it.name }) // [Alex, Jordan, Sam]
reduce and fold
reduce
Combines all elements into one value:
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.reduce { acc, num -> acc + num }
println(sum) // 15
val product = numbers.reduce { acc, num -> acc * num }
println(product) // 120
reduce starts with the first element. acc is the accumulated result so far.
fold
Same as reduce but with an initial value:
val numbers = listOf(1, 2, 3, 4, 5)
val sumPlus100 = numbers.fold(100) { acc, num -> acc + num }
println(sumPlus100) // 115
val sentence = listOf("Kotlin", "is", "great")
val joined = sentence.fold("") { acc, word ->
if (acc.isEmpty()) word else "$acc $word"
}
println(joined) // Kotlin is great
zip
Combines two collections into pairs:
val names = listOf("Alex", "Sam", "Jordan")
val scores = listOf(95, 87, 92)
val paired = names.zip(scores)
println(paired) // [(Alex, 95), (Sam, 87), (Jordan, 92)]
Zip with transform:
val formatted = names.zip(scores) { name, score -> "$name: $score" }
println(formatted) // [Alex: 95, Sam: 87, Jordan: 92]
Unzip:
val (unzippedNames, unzippedScores) = paired.unzip()
println(unzippedNames) // [Alex, Sam, Jordan]
println(unzippedScores) // [95, 87, 92]
associate
Convert a list to a map:
val students = listOf(
Student("Alex", 10, 95.0),
Student("Sam", 10, 87.0),
Student("Jordan", 11, 92.0)
)
val nameToScore = students.associate { it.name to it.score }
println(nameToScore) // {Alex=95.0, Sam=87.0, Jordan=92.0}
associateWith maps each element to a value:
val names = listOf("Alex", "Sam", "Jordan")
val nameLengths = names.associateWith { it.length }
println(nameLengths) // {Alex=4, Sam=3, Jordan=6}
Chaining Operations
The real power of Kotlin collections comes from chaining operations together. Each operation returns a new collection that you can process further.
Find the Average Score of Grade 10 Students
val avg = students
.filter { it.grade == 10 }
.map { it.score }
.average()
println(avg) // 88.33
Get Names of Top 3 Students
val top3 = students
.sortedByDescending { it.score }
.take(3)
.map { it.name }
println(top3) // [Alex, Jordan, Sam]
Count Passing Students Per Grade
val passingByGrade = students
.filter { it.score >= 80 }
.groupBy { it.grade }
.mapValues { (_, group) -> group.size }
println(passingByGrade) // {10=3, 11=1}
Find the Best Student
val best = students.maxByOrNull { it.score }
println("${best?.name} with ${best?.score}") // Alex with 95.0
Other Useful Functions
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// any — at least one matches
numbers.any { it > 8 } // true
// all — all match
numbers.all { it > 0 } // true
// none — none match
numbers.none { it < 0 } // true
// count — how many match
numbers.count { it % 2 == 0 } // 5
// sum, average, min, max
numbers.sum() // 55
numbers.average() // 5.5
numbers.min() // 1
numbers.max() // 10
// take and drop
numbers.take(3) // [1, 2, 3]
numbers.drop(7) // [8, 9, 10]
numbers.takeLast(3) // [8, 9, 10]
// distinct
listOf(1, 2, 2, 3, 3, 3).distinct() // [1, 2, 3]
// chunked — split into groups
numbers.chunked(3) // [[1,2,3], [4,5,6], [7,8,9], [10]]
// windowed — sliding window over elements
numbers.windowed(3) // [[1,2,3], [2,3,4], [3,4,5], ..., [8,9,10]]
numbers.windowed(3, step = 2) // [[1,2,3], [3,4,5], [5,6,7], [7,8,9]]
// reversed
numbers.reversed() // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// joinToString
listOf("Alex", "Sam", "Jordan").joinToString(", ") // "Alex, Sam, Jordan"
// partition — split into two lists
val (even, odd) = numbers.partition { it % 2 == 0 }
println(even) // [2, 4, 6, 8, 10]
println(odd) // [1, 3, 5, 7, 9]
Summary
| Function | Description |
|---|---|
listOf() | Read-only list |
mutableListOf() | Mutable list |
setOf() | Read-only set (no duplicates) |
mapOf() | Read-only map (key-value pairs) |
filter {} | Keep elements matching condition |
map {} | Transform each element |
flatMap {} | Transform and flatten |
groupBy {} | Group by key |
sortedBy {} | Sort by property |
reduce {} | Combine into one value |
fold(init) {} | Combine with initial value |
zip() | Pair elements from two lists |
associate {} | Convert to map |
any {} / all {} / none {} | Check conditions |
take(n) / drop(n) | First n / skip first n |
chunked(n) | Split into groups of n |
windowed(n) | Sliding window of size n |
partition {} | Split into two lists |
Source Code
You can find the complete source code for this tutorial on GitHub:
What’s Next?
Congratulations! You have completed the Foundation series of the Kotlin Tutorial. You now know:
- What Kotlin is and why developers love it
- How to install Kotlin and run programs
- Variables, types, and type inference
- Null safety
- Functions
- Control flow
- Classes and objects
- Collections
With these foundations, you are ready for the Intermediate series. In the next tutorial, you will learn about lambdas and higher-order functions — one of Kotlin’s most powerful features.
This is part 8 of the Kotlin Tutorial series. Check out Part 7: Classes if you missed it. Need a quick reference? See the Kotlin Cheat Sheet.