This episode masters Kotlin collections and the standard library: List, Set, and Map with their collection operations, higher-order functions like map, filter, fold, and flatMap, sequences with lazy evaluation, plus string manipulation and standard utility functions.

You mastered OOP in episode 4. Now it's time to leverage Kotlin's greatest strength: the standard library with its extremely rich collection API. With the combination of List, Set, Map, and higher-order functions, you can write data transformations that take dozens of lines in Java as a single expression.
Kotlin's standard library is designed functionally: instead of writing manual loops, you express intent with filter, map, and fold. The code becomes declarative, shorter, and easier to test.
Episode 5 will build the habits you use throughout the series — from episode 6 all the way to episode 22.
Kotlin distinguishes read-only and mutable collections: listOf and mutableListOf, setOf and mutableSetOf, mapOf and mutableMapOf. Read-only collections can't be modified, though their contents remain visible:
val daftar = listOf("apel", "pisang", "ceri")
val unik = setOf(1, 1, 2, 3)
val harga = mapOf("kopi" to 15000, "teh" to 8000)
println(daftar[0])
println(unik) // [1, 2, 3]
println(harga["kopi"])setOf automatically removes duplicates, and mapOf stores key-value pairs written with the infix to. Note that these collections are read-only — to add elements, use the mutable variants.
The important distinction: List and MutableList are two different interfaces. A function accepting List promises not to modify the contents; a function that needs to modify uses MutableList. This discipline makes function contracts clearer and prevents unexpected side effects.
The most frequently used collection operations are map for transformation and filter for filtering. Both accept a lambda as an argument:
val angka = listOf(1, 2, 3, 4, 5, 6)
val kuadrat = angka.map { it * it }
val genap = angka.filter { it % 2 == 0 }
println(kuadrat) // [1, 4, 9, 16, 25, 36]
println(genap) // [2, 4, 6]The lambda { it * it } uses the implicit name it for a single element. The filter expression above essentially runs a loop with a clear intent — far more readable than a manual loop. You can test all the examples with kotlinc Koleksi.kt -include-runtime -d koleksi.jar and run the resulting JAR.
fold accumulates all elements into a single value starting from a given initial value. flatMap merges transformation results that are collections into one flat list:
val angka = listOf(1, 2, 3, 4, 5)
val total = angka.fold(0) { akumulator, nilai -> akumulator + nilai }
val pasangan = listOf("ab", "cd").flatMap { it.toList() }
println(total) // 15
println(pasangan) // [a, b, c, d]fold(0) uses 0 as the initial value, then the lambda receives the accumulator and the element. flatMap flattens the results — a pattern often used to build lists from one-to-many relationships.
With a regular collection, each operation immediately creates a new collection. A map then filter chain processes all elements twice and allocates intermediate collections. Sequence solves this with lazy evaluation: elements are processed one by one only when needed:
val hasil = (1..1_000_000)
.asSequence()
.filter { it % 3 == 0 }
.map { it * it }
.take(5)
.toList()
println(hasil)With asSequence(), the filter and map operations wait until take(5) and toList() are called. For large datasets or long operation chains, sequences save allocations and time — a mental yardstick to hold onto from now on.
For small collections, sequences actually add overhead. The rule of thumb: use regular collections for small, simple data; switch to sequences for large data, long operation chains, or when taking only a small portion of the results.
The standard library provides many concise string functions. Some of the most commonly used:
val teks = " Kotlin itu Keren "
println(teks.trim())
println(teks.uppercase())
println(teks.lowercase())
println(teks.replace("Keren", "Powerful"))
println("a,b,c".split(","))
println("hello".repeat(3))trim, uppercase, replace, split, and repeat are part of the string toolbox. There are also contains, startsWith, substringAfter, and removeSuffix that you'll keep encountering in production code.
Other collection utility functions complete your arsenal:
val daftar = (1..10).toList()
println(daftar.take(3)) // [1, 2, 3]
println(daftar.drop(7)) // [8, 9, 10]
println(daftar.chunked(3)) // groups of 3
println(daftar.zip(daftar)) // element pairstake and drop slice from the front, chunked breaks the list into equally sized groups, and zip merges two collections into pairs. Combining these utilities turns processing logic that would normally be long into one-line expressions.
Episode 5 unlocks the power of collections and the standard library: List, Set, and Map with read-only contracts, higher-order functions like map, filter, fold, and flatMap, sequences for lazy evaluation on large data, and a variety of string and collection utility functions.
The key takeaways:
listOf and mutable ones with mutableListOf.map transforms, filter filters, fold accumulates.flatMap merges transformation results into a flat list.In episode 6 we'll discuss null safety and the type system — nullable types and the safe call operator, the elvis operator and non-null assertions, smart casts and type inference, plus inline classes and value classes that make your code null-safe from compilation onward.