Collection operations

Kotlin · Practical · 14 min

Most business logic is data transformation; the collection API turns loops into clear pipelines.

Kotlin's standard library has a rich set of extension functions on collections. Each returns a new collection:

  • Transform: map, mapNotNull, flatMap, mapIndexed.
  • Filter: filter, filterNot, filterIsInstance<T>(), take, drop, distinct, distinctBy.
  • Search: first { }, firstOrNull { }, find, any, all, none, count { }.
  • Aggregate: sum, sumOf { }, maxByOrNull, minOf, average, fold(initial) { acc, x -> }, reduce.
  • Group: groupBy { key } gives Map<K, List<T>>; associateBy, associateWith, partition { } gives a pair of lists; chunked, windowed, zip.
  • Sort: sorted(), sortedBy { }, sortedWith(compareBy({ it.city }, { it.name })).
  • Output: joinToString, toSet, toMap.

Collection operations are eager: each step builds an intermediate list. For long chains over large data, or infinite data, use a sequence: list.asSequence().filter { }.map { }.take(10).toList(). Sequences process elements one at a time lazily, like Java streams, and stop as soon as possible.

first() throws on an empty match; firstOrNull() returns null. Prefer sumOf { it.price } over map { it.price }.sum(). Readability first: a short for loop is fine when a chain would get clever.

Example

data class Person(val name: String, val city: String, val age: Int)

fun main() {
    val people = listOf(
        Person("Ada", "London", 36), Person("Alan", "London", 41),
        Person("Grace", "NYC", 85), Person("Linus", "Helsinki", 28),
    )
    println(people.filter { it.age > 30 }.map { it.name }.sorted()) // [Ada, Alan, Grace]
    println(people.groupBy { it.city }.mapValues { it.value.size }) // {London=2, NYC=1, Helsinki=1}
    println(people.sumOf { it.age })                   // 190
    println(people.maxByOrNull { it.age }?.name)       // Grace
    val (young, old) = people.partition { it.age < 40 }
    println("${young.size} ${old.size}")               // 2 2
    println(people.associateBy { it.name }["Linus"]?.city) // Helsinki
    println(listOf(1, 2, 3).fold(10) { acc, x -> acc + x }) // 16
    println((1..10).chunked(4))          // [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]

    val firstSquares = generateSequence(1) { it + 1 }
        .map { it * it }.filter { it % 2 == 1 }.take(3).toList()
    println(firstSquares)                              // [1, 9, 25]
}

Remember

  • map/filter/groupBy/fold build new collections
  • Prefer firstOrNull, maxByOrNull to avoid exceptions on empty input
  • Collection operations are eager; asSequence() makes them lazy
  • Use sumOf, associateBy, partition instead of manual loops

Watch out for

  • Chaining many operations over huge lists without asSequence(), creating large intermediate lists.

Check yourself

  1. What does listOf(1, 2, 3, 4).partition { it % 2 == 0 } return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: kotlinlang.org/docs/collection-transformations.html