Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
86: Sequences vs Collections Performance
Imagine you're running a sandwich shop. You have a huge order for 100 sandwiches. You've got three stations: one person toasts the bread, the next adds the fillings, and the last wraps them in paper.
If you work like a standard Kotlin Collection, you're doing things "eagerly." The first person toasts all 100 slices of bread and piles them onto a massive tray. Then, the second person takes that whole tray and adds fillings to all 100, piling them onto another massive tray. Finally, the third person wraps all 100. You need a lot of counter space (memory) to hold those intermediate trays, and if the customer suddenly says, "Actually, I only wanted the first sandwich," you've wasted a ton of effort preparing 99 others.
Now, imagine working as a Sequence. This is "lazy" evaluation. The first person toasts one slice and hands it immediately to the second person. They add the filling and hand it to the third person, who wraps it. One finished sandwich is delivered. If the customer says, "I only want one," you stop right there. You never toasted the other 99 slices, and you never needed those giant intermediate trays.
The Hidden Cost of Intermediate Lists
In Kotlin, whenever you call .filter or .map on a List, Kotlin creates a brand new list to hold the results of that specific step. If you have a chain of five operations on a list of 10,000 items, you might accidentally create five different lists in memory before you even get your final result. I've seen this cause surprising memory spikes in Android apps when developers process large datasets from a database.
val result = bigList
.filter { it.isActive } // Creates a new list
.map { it.toDto() } // Creates another new list
.take(10) // Finally, we only wanted 10!
In the example above, we've processed the entire list twice just to keep 10 items. That's a lot of wasted CPU cycles and garbage collection pressure.
Switching to a Lazy Pipeline
To stop this, we use asSequence(). This tells Kotlin: "Don't execute these steps immediately. Just remember the plan, and only process the items when I actually ask for the final result."
val result = bigList
.asSequence() // Start the lazy pipeline
.filter { it.isActive } // No list created here
.map { it.toDto() } // No list created here
.take(10) // Still no list!
.toList() // Terminal operation: Now we actually execute
The .toList() (or .first(), .count(), etc.) is what we call a terminal operation. Nothing happens until you hit a terminal operation. Once you do, the items flow through the pipeline one by one. The first item that passes the filter is immediately mapped and sent to the take(10) bucket. Once that bucket hits 10, the whole process stops. The remaining thousands of items are never even looked at.
When Eager is Actually Faster
You might be wondering, "Why wouldn't I just use sequences for everything?" Well, there's a trade-off. Setting up the sequence machinery—the state tracking and the function wrapping—has a small overhead.
If you're dealing with a tiny list (say, 20 items) and only one or two operations, the overhead of creating a Sequence object is actually more expensive than just creating one intermediate list. In my experience, if your data set is small, stick with Collections. If you're doing complex chaining or working with large datasets, asSequence() is your best friend.
📋 Practical Task
Optimizing a High-Volume Log Filter
You are building a log analyzer that processes a list of 100,000 LogEntry objects. The goal is to find the first 5 "ERROR" level logs that contain the word "Timeout" in their message, and then extract just the timestamp of those logs.
Currently, the code is written using standard eager Collections, causing a noticeable lag and high memory usage. Your task is to refactor the following function to use a Sequence to ensure that the program stops processing as soon as the 5th match is found, avoiding the creation of unnecessary intermediate lists.
data class LogEntry(val level: String, val message: String, val timestamp: Long)
fun getFirstFiveTimeoutErrors(logs: List<LogEntry>): List<Long> {
return logs
.filter { it.level == "ERROR" }
.filter { it.message.contains("Timeout") }
.map { it.timestamp }
.take(5)
}
Requirement: Modify the function to implement lazy evaluation and return the result as a List<Long>.
There are no comments for now.