Kotlin Collections vs Sequences
Table of Contents
🧐 The Performance Dilemma in Lists
In modern Android development, we manipulate lists constantly: API responses, contact lists, shopping cart items. Kotlin offers a rich and expressive collections API (map, filter, groupBy), but naive usage can skyrocket memory and CPU consumption.
The key question is: When should I use a standard List (Iterable) and when a Sequence?
Eager vs Lazy Evaluation
To understand the difference, we must look at how operations are executed.
Collections (Iterable): Eager Evaluation
Each operation creates a complete intermediate collection.
val list = listOf(1, 2, 3, 4, 5)
val result = list
.map { it * 2 } // Creates a new temporary list: [2, 4, 6, 8, 10]
.filter { it > 5 } // Creates ANOTHER temporary list: [6, 8, 10]
.first() // Returns 6
In this simple example, it’s not serious. But if the list had 1 million elements, you would be creating two lists of 1 million integers in memory just to get the first result. 💥
Sequences: Lazy Evaluation
Operations are not executed until the final result (terminal operation) is requested. Additionally, they process element by element.
val result = list.asSequence()
.map { it * 2 }
.filter { it > 5 }
.first()
Execution Flow:
- Takes
1->map(1*2=2)->filter(2 > 5? No)-> Discarded. - Takes
2->map(2*2=4)->filter(4 > 5? No)-> Discarded. - Takes
3->map(3*2=6)->filter(6 > 5? Yes)->first()takes it and terminates.
4 and 5 are not processed! And most importantly: No intermediate lists were created.
🛠️ When to use Sequences in Android
Don’t use Sequence for everything. They have an overhead of creating wrapper objects.
Golden Rule:
Use Sequence if:
- The list is large (tens of thousands of elements).
- You have multiple chained steps (
map+filter+sorted…). - You use “short-circuit” operations like
first,take,any, where you don’t need to process the entire list.
If the list is small (e.g., 100 items in a RecyclerView), the standard List is faster due to lower overhead.
⚡ Key Functional Operations
Beyond map and filter, mastering these operations cleans up your code:
1. fold and reduce
To accumulate a result from a list.
data class Product(val price: Double, val quantity: Int)
val cart = listOf(Product(10.0, 2), Product(5.0, 3))
// Reduce: The accumulator starts with the first element (can throw exception if empty)
// Fold: You define the initial value of the accumulator (safer)
val total = cart.fold(0.0) { acc, product ->
acc + (product.price * product.quantity)
}
// Result: 35.0
2. associateBy and groupBy
Vital for optimizing searches. Converting a List to a Map reduces search complexity from O(N) to O(1).
// Inefficient: O(N^2) Search
val users = getUsers() // List<User>
val orders = getOrders() // List<Order> (has userId)
orders.forEach { order ->
val user = users.find { it.id == order.userId } // Traverses the entire user list for each order
// ...
}
// Efficient: O(N) Search
val userMap = users.associateBy { it.id } // Map<String, User>
orders.forEach { order ->
val user = userMap[order.userId] // Instant lookup
// ...
}
3. zip
Combines two lists pair by pair.
val names = listOf("Alice", "Bob")
val scores = listOf(95, 80)
val results = names.zip(scores) { name, score ->
"$name scored $score"
}
// ["Alice scored 95", "Bob scored 80"]
🚫 Common Anti-Patterns
1. Unnecessary sorting (sorted) in Sequences
The sorted operation is a “stateful intermediate operation”. To sort, the sequence MUST process all elements and load them into memory, nullifying much of the lazy evaluation benefit.
If you are going to sort, do it at the end or evaluate if you really need a sequence.
2. Breaking the Sequence Chain
list.asSequence()
.map { ... }
.toList() // ❌ Materializes the list
.filter { ... } // ❌ Creates another list again
.first()
Keep the sequence (asSequence) until the final terminal operation.
🎯 Conclusion
Collections in Kotlin are powerful, but understanding their internal implementation distinguishes a Junior developer from a Senior.
- Small lists and simple operations: Use standard
List. - Large lists, long chains, or short-circuits: Use
asSequence(). - Frequent searches: Convert to
MapwithassociateBy.
Optimizing memory usage in Android not only avoids OutOfMemoryError, but reduces Garbage Collector frequency, resulting in a smoother UI (less “jank”).
You might also be interested in
Socratic Prompting: Maieutic Reasoning in Mobile Dev
Avoid 'almost correct' code in Android & iOS. Master Socratic and Maieutic prompting with Gemini, Copilot, and Cursor using dialog flows and source code.
Koin vs Hilt: Mobile Architecture in 2026
Discover how to choose between Koin and Hilt for your next Android or Kotlin Multiplatform app, analyzing scalability, performance, and real use cases.
Room Database: Scalability and Performance in Android
Discover the hidden performance issues when scaling Room Database on Android and learn how to mitigate them using indexes and external libraries.