Skip to content
ArceApps Logo ArceApps
ES

Kotlin Collections vs Sequences

4 min read
Kotlin Collections vs Sequences

🧐 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:

  1. Takes 1 -> map(1*2=2) -> filter(2 > 5? No) -> Discarded.
  2. Takes 2 -> map(2*2=4) -> filter(4 > 5? No) -> Discarded.
  3. 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:

  1. The list is large (tens of thousands of elements).
  2. You have multiple chained steps (map + filter + sorted…).
  3. 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 Map with associateBy.

Optimizing memory usage in Android not only avoids OutOfMemoryError, but reduces Garbage Collector frequency, resulting in a smoother UI (less “jank”).

Share this post:

You might also be interested in

Socratic Prompting: Maieutic Reasoning in Mobile Dev
AI July 13, 2026

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.

Read more
Koin vs Hilt: Mobile Architecture in 2026
Kotlin June 28, 2026

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.

Read more
Room Database: Scalability and Performance in Android
Android June 25, 2026

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.

Read more