Skip to main content

Kotlin collection operators mental map

This page converts the shared PDF into a searchable web lesson. The PDF is still available here: Download the Kotlin Collection Operators mental map.

How to choose an operator

Do not start by memorizing function names. First ask what problem you are solving: keep values, change values, find one value, check a condition, group data, sort data, combine data, or cut data into batches.

The complete mental map​

QuestionOperators
Which elements do I want?filter, filterNot, filterIndexed, filterNotNull, filterIsInstance, partition
How do I cut from the start or end?take, takeWhile, takeLast, takeLastWhile, drop, dropWhile, dropLast, dropLastWhile
How do I remove duplicates?distinct, distinctBy
What should each element become?map, mapIndexed, mapNotNull, mapIndexedNotNull
How do I flatten nested collections?flatMap, flatMapIndexed, flatten
How do I get one element?first, firstOrNull, last, lastOrNull, single, singleOrNull, find, findLast
How do I access by position?get, [index], getOrNull, elementAt, elementAtOrNull, elementAtOrElse
How do I pick randomly?random, randomOrNull
How do I check conditions?any, all, none
How do I count or calculate?count, sum, sumOf, average, minOrNull, maxOrNull, minByOrNull, maxByOrNull, fold, reduce
How do I sort?sorted, sortedDescending, sortedBy, sortedByDescending, sortedWith, reversed
How do I create maps or groups?groupBy, groupingBy, eachCount, associate, associateBy, associateWith
How do I combine collections?zip, unzip, zipWithNext, plus, minus
How do I create batches/windows?chunked, windowed, windowedSequence, runningFold, runningReduce

Example model used by the labs​

data class Student(
val name: String,
val age: Int,
val courses: List<String>,
val marks: Int
)

fun sampleStudents() = listOf(
Student("Arun", 22, listOf("Kotlin", "Android"), 80),
Student("Priya", 24, listOf("Java", "Kotlin"), 90),
Student("Rahul", 21, listOf("Android", "Flutter"), 70),
Student("Meena", 23, listOf("Kotlin", "Compose"), 85)
)
Open in Kotlin Playground

Filtering and slicing​

Filtering checks every element. takeWhile and dropWhile stop at a boundary. This is the most common interview trap.

fun main() {
val students = sampleStudents()
val numbers = listOf(2, 4, 1, 6, 8)

println("passed = " + students.filter { it.marks >= 80 }.map { it.name })
println("not passed = " + students.filterNot { it.marks >= 80 }.map { it.name })
println("indexed = " + students.filterIndexed { index, _ -> index % 2 == 0 }.map { it.name })
println("partition = " + students.partition { it.marks >= 80 })
println("takeWhile = " + numbers.takeWhile { it % 2 == 0 })
println("dropWhile = " + numbers.dropWhile { it % 2 == 0 })
println("takeLastWhile = " + numbers.takeLastWhile { it % 2 == 0 })
println("dropLastWhile = " + numbers.dropLastWhile { it % 2 == 0 })
}
Open in Kotlin Playground

Use filter when matching elements can appear anywhere. Use takeWhile or dropWhile only when the list is already ordered around a boundary, such as "new unread notifications from the top".

Transforming​

map keeps the same number of rows. mapNotNull may reduce rows. flatMap turns one row into many rows and then flattens the result.

fun main() {
val students = sampleStudents()

println("names = " + students.map { it.name })
println("numbered = " + students.mapIndexed { index, student -> "$index-${student.name}" })
println("valid numbers = " + listOf("10", "bad", "20").mapNotNull { it.toIntOrNull() })
println("indexed valid = " + listOf("10", "bad", "20").mapIndexedNotNull { index, value ->
value.toIntOrNull()?.let { index to it }
})
println("all courses = " + students.flatMap { it.courses })
println("unique courses = " + students.flatMap { it.courses }.distinct())
}
Open in Kotlin Playground

Android example: API models often contain nullable fields. mapNotNull is acceptable when dropping incomplete rows is a deliberate UI rule. If incomplete rows are server bugs, collect validation errors instead.

Accessing and searching​

The OrNull versions are for expected absence. Throwing versions are for invariants.

fun main() {
val students = sampleStudents()
val empty = emptyList<Student>()

println("first = " + students.first().name)
println("firstOrNull = " + empty.firstOrNull())
println("last = " + students.last().name)
println("singleOrNull one = " + listOf("only").singleOrNull())
println("singleOrNull many = " + listOf("one", "two").singleOrNull())
println("list[1] = " + students[1].name)
println("get(1) = " + students.get(1).name)
println("getOrNull(10) = " + students.getOrNull(10))
println("elementAtOrElse = " + students.elementAtOrElse(10) { Student("Fallback", 0, emptyList(), 0) }.name)
println("find = " + students.find { it.name == "Priya" }?.name)
println("randomOrNull = " + empty.randomOrNull())
}
Open in Kotlin Playground

Prefer list[index] for normal list access. Prefer getOrNull(index) when an index comes from UI state and may be stale. Prefer single() only when exactly one result is a real business rule.

Checking and aggregation​

any, all, and none short-circuit. fold is safer than reduce when empty input is possible.

fun main() {
val students = sampleStudents()
val emptyMarks = emptyList<Int>()

println("any high = " + students.any { it.marks > 85 })
println("all passed = " + students.all { it.marks >= 40 })
println("none failed = " + students.none { it.marks < 40 })
println("count passed = " + students.count { it.marks >= 80 })
println("sumOf marks = " + students.sumOf { it.marks })
println("average = " + students.map { it.marks }.average())
println("min age = " + students.minByOrNull { it.age }?.name)
println("max marks = " + students.maxByOrNull { it.marks }?.name)
println("fold empty = " + emptyMarks.fold(0) { total, mark -> total + mark })
println("reduce empty failure = " + runCatching { emptyMarks.reduce { total, mark -> total + mark } }.exceptionOrNull()!!::class.simpleName)
}
Open in Kotlin Playground

Common trap: emptyList<Int>().all { it > 0 } returns true. If at least one item must exist, check items.isNotEmpty() && items.all { ... }.

Sorting, grouping, and map creation​

groupBy keeps all values per key. associateBy keeps one value per key and later duplicates replace earlier values.

fun main() {
val students = sampleStudents()

println("sorted age = " + students.sortedBy { it.age }.map { it.name })
println("sorted marks desc = " + students.sortedByDescending { it.marks }.map { it.name })
println("custom sort = " + students.sortedWith(compareBy<Student> { it.age }.thenBy { it.name }).map { it.name })
println("groupBy age = " + students.groupBy { it.age })
println("course counts = " + students.flatMap { it.courses }.groupingBy { it }.eachCount())
println("associateBy name = " + students.associateBy { it.name })
println("associate name to marks = " + students.associate { it.name to it.marks })
println("associateWith marks = " + students.associateWith { it.marks })
}
Open in Kotlin Playground

Android example: use associateBy(User::id) for fast lookup by stable ID, but validate duplicates if duplicate IDs indicate corrupt API data.

Combining, chunking, and running results​

zip combines by position and stops at the shorter collection. chunked creates non-overlapping groups. windowed creates overlapping groups.

fun main() {
val names = listOf("Arun", "Priya", "Rahul")
val marks = listOf(80, 90)
val numbers = listOf(1, 2, 3, 4, 5)

println("zip = " + names.zip(marks))
println("unzip = " + listOf("Arun" to 80, "Priya" to 90).unzip())
println("zipWithNext = " + numbers.zipWithNext())
println("plus = " + (listOf(1, 2) + 3))
println("minus = " + (listOf(1, 2, 3, 2) - 2))
println("chunked = " + numbers.chunked(2))
println("windowed = " + numbers.windowed(3))
println("runningFold = " + numbers.runningFold(0) { total, n -> total + n })
println("runningReduce = " + numbers.runningReduce { total, n -> total + n })
}
Open in Kotlin Playground

Use chunked(100) for API batch limits. Use windowed(3) for moving averages or comparing neighboring values. Use runningFold when the UI needs every intermediate state, not just the final total.

Interview priority​

Master these first: filter, map, flatMap, distinct, sortedBy, find, firstOrNull, getOrNull, any, all, none, count, sumOf, minByOrNull, maxByOrNull, groupBy, associateBy, partition, zip, chunked, windowed, fold, and reduce.

Interview questions​

  1. Why is filter different from takeWhile?
  2. Why can associateBy be dangerous with duplicate keys?
  3. Why does reduce fail on empty input but fold does not?
  4. Why does zip stop at the shorter list?
  5. When should you use single() instead of first()?
  6. Why should stale UI indexes usually use getOrNull()?
  7. How would you batch 10,000 pending IDs into API calls of 100 IDs each?
  8. How would you calculate a moving average with windowed()?