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
70: Comparing Kotlin Idioms to Java Equivalents
If you've spent a few years in the Java ecosystem, you probably have "Java brain." There's nothing wrong with that—Java taught us a lot about structure and types—but when you first move to Kotlin, it's incredibly easy to write "Java with Kotlin syntax." I see it all the time in code reviews: a developer uses val and var, but they're still writing nested null checks and manual for-loops. It works, and the compiler is happy, but you're missing out on the actual power of the language.
Fighting the Nulls with Java Habits
In Java, we're conditioned to be terrified of NullPointerException, so we write defensive guards. If I were writing this in Java, I'd probably check if a user object exists, then check if their profile exists, and then finally grab the email. In naive Kotlin, you might see something like this:
if (user != null) {
val profile = user.profile
if (profile != null) {
val email = profile.email
if (email != null) {
sendEmail(email)
}
}
}
This is what I call "The Pyramid of Doom." It's visually noisy and forces the reader to keep track of three levels of indentation just to find the actual business logic. In idiomatic Kotlin, we treat nullability as a first-class citizen using the safe-call operator and let. I'd rewrite that whole block as a single chain:
user?.profile?.email?.let { email ->
sendEmail(email)
}
The trade-off here is a shift in mindset. Instead of "guarding" the code, you're "streaming" the data. If any link in that chain is null, the whole expression simply evaluates to null and the let block never executes. It's cleaner, and more importantly, it's harder to accidentally forget a null check because the type system forces your hand.
The Verbosity of Manual Loops
Another habit that lingers is the imperative approach to collections. I often see developers create a mutable list, loop through a source collection, check a condition, and manually add items to that new list. It's a pattern we've used for decades.
val activeUsers = mutableListOf<User>()
for (user in users) {
if (user.isActive && user.age > 18) {
activeUsers.add(user)
}
}
While this is performant, it's purely procedural. It tells the computer how to do it, rather than what you want. Kotlin's standard library is designed to handle this functionally. I'd suggest using filter instead. It eliminates the need for a mutable temporary list and makes the intent immediately clear to anyone glancing at the code:
val activeUsers = users.filter { it.isActive && it.age > 18 }
Now, you might worry about the overhead of creating intermediate collections. In most business applications, that cost is negligible compared to the gain in readability. If you're working with a massive dataset where performance is critical, you can just slap .asSequence() onto the front of the chain to make it lazy, giving you the best of both worlds.
Cleaning up Object Configuration
Finally, let's talk about object setup. In Java, we often have a series of setter calls after instantiating an object. Even in Kotlin, I see people doing this:
val request = ApiRequest()
request.url = "https://api.example.com"
request.timeout = 30
request.retryCount = 3
service.execute(request)
It's not "wrong," but it's repetitive. You're typing request over and over again. This is where the scope function apply becomes your best friend. It changes the scope of this to the object being configured, allowing you to group all the initialization logic together.
val request = ApiRequest().apply {
url = "https://api.example.com"
timeout = 30
retryCount = 3
}
service.execute(request)
I prefer this because it visually encapsulates the configuration. When I see an apply block, I know exactly where the object setup begins and ends, and the resulting request variable is cleanly assigned the fully configured object in one expression.
📋 Practical Task
Refactoring the Legacy Order Processor
You've inherited a piece of code written by a developer who recently switched from Java to Kotlin. The code works, but it's written in a very imperative, non-idiomatic style. Your task is to refactor the processOrders function to use Kotlin idioms.
Requirements:
- Replace the nested null checks with a safe-call chain and
let. - Replace the
forloop and mutable list with afilterandmapchain. - Use
applyto configure theOrderResultobject.
data class Order(val id: String, val status: String?, val amount: Double?)
data class OrderResult(var orderId: String = "", var finalAmount: Double = 0.0, var processed: Boolean = false)
fun processOrders(orders: List<Order>): List<OrderResult> {
val results = mutableListOf<OrderResult>()
for (order in orders) {
if (order.status != null) {
if (order.status == "COMPLETED") {
if (order.amount != null) {
val result = OrderResult()
result.orderId = order.id
result.finalAmount = order.amount
result.processed = true
results.add(result)
}
}
}
}
return results
}There are no comments for now.