Kotlin Idiomatic Collections — The Ultimate Guide

HR: how much experience do you have in Kotlin? 🤔
Le Candidate: 3 Yrs 😒
Both are happy, it seems. Do you really feel, you are using enough Kotlin having 3/4/5 yrs experience in Android Development? Or maybe just 30–35% of it.
Once, I tried to dig more about Kotlin Concepts, I realized, I’m a Pseudo Kotlin Dev, it has so many powerful methods/extension function and lot many things I never encountered with.
While solving DSA Question, just seeing an array, first thing comes into the mind that let’s write a for { } Loop 😂. If you’re solving the problem in Kotlin, there’s many alternatives. In this article, We’ll be going through all the Kotlin Idiomatic which is going to turn the table for us.
Kotlin’s standard library provides a rich set of collection operations that allow you to write concise, readable, safe, and efficient code. Using idiomatic Kotlin helps you avoid boilerplate loops, manual maps, and counters.
Transforming Collections: map, mapIndexed, flatMap, flatMapIndexed
Purpose: Transform each element of a collection.
val names = listOf("Alice", "Bob")
val upper = names.map { it.uppercase() } // [ALICE, BOB]val indexed = names.mapIndexed { i, n -> "$i:$n" } // [0:Alice, 1:Bob]
val nested = listOf(listOf(1,2), listOf(3,4))
val flatDoubled = nested.flatMap { it.map { it * 2 } } // [2,4,6,8]
| Function | Time Complexity | Notes / DSA Tips |
| ---------------------------- | --------------- | --------------------------------------------------------------------------------------------- |
| `map` / `mapIndexed` | O(n) | Good for transforming arrays/lists of size n. Avoid inside nested loops if n is large. |
| `flatMap` / `flatMapIndexed` | O(n + m) | Flattens nested lists; m = total size of inner lists. Useful when flattening adjacency lists. |
Real-world: Transform database entities, flatten nested order items, apply discounts to product prices.
Filtering Collections: filter, take, drop, takeWhile, dropWhile
val nums = listOf(1,2,3,4,5)
val evens = nums.filter { it % 2 == 0 } // [2,4]val firstThree = nums.take(3) // [1,2,3]val restAfterTwo = nums.drop(2) // [3,4,5]
Real-world: Select active users, paginate lists, filter events, skip processed orders.
| Function | Time Complexity | Notes |
| ------------------------- | --------------- | --------------------------------------- |
| `filter` | O(n) | Linear scan. Avoid inside nested loops. |
| `take(k)` / `drop(k)` | O(k) / O(n-k) | Efficient for slicing. |
| `takeWhile` / `dropWhile` | O(k) worst-case | Stops once condition fails. |
Searching & Boolean Checks: firstOrNull, lastOrNull, find, any, all, none
val words = listOf("apple", "banana", "cherry")
val firstB = words.firstOrNull { it.startsWith('b') } // banana
println(words.any { it.length > 5 }) // true
println(words.all { it.length > 0 }) // true
Real-world: Find a user by ID, check if any transaction failed, validate inputs.
| Function | Time Complexity | Notes |
| ---------------------- | --------------- | ------------------------------------- |
| `firstOrNull` / `find` | O(n) | Linear scan until match. |
| `lastOrNull` | O(n) | Linear from start. |
| `any` / `all` / `none` | O(n) worst-case | Stops early if condition met/not met. |
Removing Duplicates: distinct
val numbers = listOf(1,2,2,3)
val unique = numbers.distinct() // [1,2,3]
Real-world: Unique tags, email addresses, product IDs.
| Function | Time Complexity | Notes |
| ------------ | --------------- | ------------------------ |
| `distinct()` | O(n) | Internally uses HashSet. |
| `distinctBy` | O(n) | Similar, based on key. |
Grouping & Partitioning: groupBy, partition, groupingBy
val names = listOf("Alice", "Bob", "Anna")
val grouped = names.groupBy { it.first() }
// {A=[Alice, Anna], B=[Bob]}
val (even, odd) = listOf(1,2,3,4).partition { it % 2 == 0 }
// even=[2,4], odd=[1,3]val letters = listOf('a','b','a')
val counts = letters.groupingBy { it }.eachCount() // {a=2, b=1}
Real-world: Categorize products, divide tasks, count frequencies, group users by role.
| Function | Time Complexity | Notes |
| ------------------------ | --------------- | ------------------------------ |
| `groupBy` | O(n) | Linear, creates Map of lists. |
| `partition` | O(n) | Linear, splits into two lists. |
| `groupingBy.eachCount()` | O(n) | Count frequencies efficiently. |
Sorting Collections: sorted, sortedBy, sortedWith, sortWith
data class User(val name: String, val salary: Int)
val users = mutableListOf(User("John",5000), User("Alice",7000))
val sortedUsers = users.sortedWith(compareByDescending<User> { it.salary }.thenBy { it.name })
println(sortedUsers) // New sorted list
users.sortWith(compareByDescending<User> { it.salary }.thenBy { it.name })
println(users) // Original list sorted in-place
Real-world: Leaderboards, payroll, priority queues.
| Function | Time Complexity | Notes |
| ---------------------------------- | --------------- | ------------------------------ |
| `sorted`, `sortedBy`, `sortedWith` | O(n log n) | Returns new list. Stable sort. |
| `sortWith` | O(n log n) | In-place, stable. |
Aggregation & Reduction: fold, reduce, sumOf
val nums = listOf(1,2,3)
val sum = nums.fold(0) { acc, n -> acc + n } // 6
val product = nums.reduce { acc, n -> acc * n } // 6
val totalSalary = users.sumOf { it.salary } // 12000
Real-world: Total revenue, inventory valuation, product of metrics.
| Function | Time Complexity | Notes |
| ----------------- | --------------- | ------------------------------- |
| `fold` / `reduce` | O(n) | Linear traversal, accumulation. |
| `sumOf` | O(n) | Linear sum of values. |
Mapping to Keys & Values: associateBy, associateWith, zip
val mapByName = users.associateBy { it.name }
// {"John"=User("John",5000), "Alice"=User("Alice",7000)}
val scores = listOf(90, 80)
val zipped = listOf("Alice", "Bob").zip(scores) { name, score -> "$name scored $score" }
// ["Alice scored 90", "Bob scored 80"]
Real-world: Lookup maps, report generation, pairing names with values.
| Function | Time Complexity | Notes |
| ------------------------------- | --------------- | ---------------------------- |
| `associateBy` / `associateWith` | O(n) | Creates a HashMap; linear. |
| `zip` | O(n) | Linear pairwise combination. |
Flattening Collections: flatten, flatMap
val nested = listOf(listOf(1,2), listOf(3,4))
val flat = nested.flatten() // [1,2,3,4]val flatDoubled = nested.flatMap { it.map { it*2 } } // [2,4,6,8]
Real-world: Flatten orders, transform items in batch operations.
| Function | Time Complexity | Notes |
| ----------- | --------------- | --------------------------- |
| `flatten()` | O(n) | n = total elements |
| `flatMap()` | O(n + m) | Linear with transformation. |
Lazy Evaluation: asSequence
val numbers = (1..1_000_000).asSequence()
val result = numbers.map { it * 2 }
.filter { it % 3 == 0 }
.take(5)
.toList()
println(result)
Real-world: Process huge datasets efficiently, stream logs, large computations.
| Function | Time Complexity | Notes |
| ------------ | --------------- | ------------------------------------------------------------ |
| `asSequence` | O(1) creation | Evaluation is lazy; terminal operation triggers actual work. |
Sliding Windows / Batching: windowed, chunked
val numbers = (1..5).toList()
println(numbers.windowed(3)) // [[1,2,3],[2,3,4],[3,4,5]]println(numbers.chunked(2)) // [[1,2],[3,4],[5]]
Real-world: Analytics, rolling averages, batch processing.
| Function | Time Complexity | Notes |
| ------------- | --------------- | --------------------------------------- |
| `windowed(k)` | O(n*k) | Each window copied, beware for large k. |
| `chunked(k)` | O(n) | Linear, slices into batches. |
Index-aware Iteration: withIndex, mapIndexed
val names = listOf("Alice", "Bob")
for ((index, name) in names.withIndex()) {
println("$index -> $name")
}
Real-world: Assign positions, show rankings, row numbers.
| Function | Time Complexity | Notes |
| ------------ | --------------- | ----------------------------- |
| `withIndex` | O(n) | Linear; no extra overhead |
| `mapIndexed` | O(n) | Transform elements with index |
Side-effects in Chains: onEach
val doubled = (1..5).map { it*2 }
.onEach { println(it) }
.toList()
Real-world: Logging, debugging, triggering events while chaining.
| Function | Time Complexity | Notes |
| -------- | --------------- | ----------------------------------- |
| `onEach` | O(n) | Linear, performs action per element |
Conditional Returns: takeIf / takeUnless
val number = 10
println(number.takeIf { it % 2 == 0 }) // 10
println(number.takeUnless { it % 2 == 0 }) // null
Real-world: Inline validation, optional filtering, pipeline-friendly.
| Function | Time Complexity | Notes |
| ----------------------- | --------------- | --------------------------- |
| `takeIf` / `takeUnless` | O(1) | Constant time, just a check |
Safe Access & Null Handling
val arr = listOf(10, 20)
val second = arr.getOrNull(1) // returns null if out of bounds
Real-world: Avoid IndexOutOfBoundsException safely.
| Function | Time Complexity | Notes |
| ----------- | --------------- | -------------------------------- |
| `getOrNull` | O(1) | Constant-time, avoids exceptions |
Infinite / Functional Sequences: generateSequence
val fib = generateSequence(Pair(0,1)) { Pair(it.second, it.first+it.second) }
.map { it.first }
.take(10)
.toList()
println(fib) // [0,1,1,2,3,5,8,13,21,34]
Real-world: Fibonacci, lazy event streams, infinite generators.
| Function | Time Complexity | Notes |
| ------------------ | ---------------- | --------------------------------- |
| `generateSequence` | O(1) per element | Lazy, infinite sequences possible |
Cheat Sheet ❤️

If you have reached till here, hoping you found this blog useful 🙌🏻.
Happy Learning!
Kotlin Idiomatic Collections — The Ultimate Guide was originally published in ProAndroidDev on Medium, where people are continuing the conversation by highlighting and responding to this story.




This Post Has 0 Comments