Collections & Functional Operators

Kotlin Fundamentals — Collections & Functional Operators
Kotlin Fundamentals
Course 1 · Chapter 6 · Collections & Functional Operators

📚 Collections & Functional Operators

Kotlin's collections come in mutable and read-only flavors as a first-class distinction, and its functional operators — map, filter, reduce, flatMap — are the primary tool for working with them. If Chapter 2's lambdas felt abstract, this is where they become the everyday way real Kotlin code processes data.

📋 List, Set, and Map

val names: List<String> = listOf("Alice", "Bob", "Alice") val uniqueNames: Set<String> = setOf("Alice", "Bob", "Alice") val ages: Map<String, Int> = mapOf("Alice" to 30, "Bob" to 25) println(names) // [Alice, Bob, Alice] — duplicates kept, order preserved println(uniqueNames) // [Alice, Bob] — duplicates removed println(ages["Alice"]) // 30

listOf, setOf, and mapOf all produce read-only collections by default — no add, remove, or index-assignment is available on them. That's a deliberate mirror of Chapter 1's val vs var distinction, applied to collections: read-only is the default, mutability is opt-in.

Mutable Versions

mutableListOf(), mutableSetOf(), and mutableMapOf() return collections that support add(), remove(), and similar. Reach for these only when a collection genuinely needs to change after creation.

to — Building Pairs

"Alice" to 30 creates a Pair("Alice", 30). It's an infix function, not special syntax — mapOf is really just a function that accepts a list of Pairs.

Collection Literals: JavaScript vs Kotlin

JavaScriptKotlin
List/Arrayconst names = ["Alice", "Bob"];val names = listOf("Alice", "Bob")
Map/Objectconst ages = {Alice: 30, Bob: 25};val ages = mapOf("Alice" to 30, "Bob" to 25)
Read-only by default?No — arrays/objects are always mutableYes — listOf/mapOf are read-only

🗺️ map — Transform Every Element

val numbers = listOf(1, 2, 3, 4) val squared = numbers.map { it * it } println(squared) // [1, 4, 9, 16]

map applies the lambda to every element and returns a new list of the results, one-to-one with the input — the same shape as JavaScript's Array.prototype.map.

🔍 filter — Keep Some Elements

val numbers = listOf(1, 2, 3, 4, 5, 6) val evens = numbers.filter { it % 2 == 0 } println(evens) // [2, 4, 6]

filter keeps only elements where the lambda returns true — Kotlin's equivalent of JavaScript's Array.prototype.filter, with the same predicate-based idea.

➕ reduce — Combine into One Value

val numbers = listOf(1, 2, 3, 4) val sum = numbers.reduce { acc, n -> acc + n } println(sum) // 10 // fold is like reduce, but takes an explicit starting value val sumFrom100 = numbers.fold(100) { acc, n -> acc + n } println(sumFrom100) // 110

reduce walks the list, accumulating a single result — acc starts as the first element, then each step combines it with the next. fold is nearly identical but takes an explicit initial value instead of using the first element, which also means fold works safely on an empty list (reduce throws on an empty collection, since it has no first element to start from).

🌊 flatMap — Map, Then Flatten

val teams = listOf(listOf("Alice", "Bob"), listOf("Carol", "Dave")) val allPlayers = teams.flatMap { it } println(allPlayers) // [Alice, Bob, Carol, Dave] — one flat list // Compare to plain map, which keeps the nesting: val stillNested = teams.map { it } println(stillNested) // [[Alice, Bob], [Carol, Dave]]

flatMap is map followed by flattening one level of nesting — useful whenever the transformation for each element produces its own list, and the goal is a single combined list rather than a list of lists.

Functional Operators Quick Reference

OperatorInput → OutputJavaScript Equivalent
mapN elements → N transformed elementsArray.prototype.map
filterN elements → subset matching a conditionArray.prototype.filter
reduce / foldN elements → 1 accumulated valueArray.prototype.reduce
flatMapN elements → N lists, flattened into 1Array.prototype.flatMap

⚡ Sequences — Lazy Evaluation

Chaining several operators on a regular collection (List, Set) creates a full intermediate list at every step. A Sequence instead evaluates lazily, element by element, through the whole chain at once:

val result = (1..1_000_000) .asSequence() .filter { it % 2 == 0 } .map { it * it } .take(5) .toList() println(result) // [4, 16, 36, 64, 100] — only computed enough elements to satisfy take(5)
⚠ Without asSequence(), Every Step Builds a Full List

The equivalent chain without .asSequence()(1..1_000_000).filter { ... }.map { ... }.take(5) — would first filter all one million numbers into a 500,000-element list, then map all of those, and only then take 5. On large or infinite ranges, that gap in work done is significant; on small lists (the vast majority of real code), plain operators are simpler and the difference doesn't matter.

💻 Coding Challenges

Challenge 1: List, Set, and Map Basics

Create a List<String> of five city names including one duplicate, convert it to a Set to remove the duplicate, and print both. Then create a Map<String, Int> of three cities to their population (in millions) and print the population of one city by key lookup.

Goal: Get comfortable creating and reading from all three core collection types.

→ Solution

Challenge 2: filter, map, and reduce Chained

Given val prices = listOf(12.50, 8.00, 25.00, 3.50, 40.00), filter to prices over 10.0, map each remaining price by applying a 20% discount, then use reduce to sum the discounted prices into a single total. Print the total.

Goal: Chain three functional operators together in one realistic calculation.

→ Solution

Challenge 3: flatMap

Given val orders = listOf(listOf("Apple", "Bread"), listOf("Milk"), listOf("Eggs", "Cheese", "Butter")) (each inner list is one customer's order), use flatMap to produce a single flat list of every item across all orders, then print how many total items were ordered using .size.

Goal: Practice flattening a list of lists into one combined list.

→ Solution

💡 Chaining Reads Like a Pipeline

Kotlin code very rarely writes a manual for-loop to transform data — chains like list.filter { ... }.map { ... }.sortedBy { ... } are the idiomatic default. Each operator returns a new collection, so the whole chain reads top-to-bottom as a data pipeline: start with the source, keep applying steps, end with the result.

🎯 What's Next

Next chapter: Control Flow & Destructuringwhen expressions, ranges, loops, and destructuring declarations.