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

FunctionDescription
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:

KT-8 Source Code on GitHub

What’s Next?

Congratulations! You have completed the Foundation series of the Kotlin Tutorial. You now know:

  1. What Kotlin is and why developers love it
  2. How to install Kotlin and run programs
  3. Variables, types, and type inference
  4. Null safety
  5. Functions
  6. Control flow
  7. Classes and objects
  8. 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.