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
85: MutableList vs List Semantics
I see this a lot when I'm reviewing code from developers moving to Kotlin from Java or TypeScript. They treat List and MutableList as if they are the same thing, just with different "permissions" on the variable. They assume that if a function returns a List, they can just cast it to a MutableList to sneak a new element in there.
The "Just Cast It" Fallacy
Here is the exact mistake I'm talking about. Imagine you're building a simple session manager that tracks active usernames. You have a function that returns the current list of users, and you want to add a new user to that list without calling a specific "add" method.
val activeUsers = listOf("Alice", "Bob")
fun getActiveUsers(): List<String> = activeUsers
fun main() {
val users = getActiveUsers()
// "I know it's actually an ArrayList under the hood, so I'll just cast it"
val mutableUsers = users as MutableList<String>
mutableUsers.add("Charlie")
}
If you run this, your app is going to crash. You'll get a ClassCastException. Why? Because listOf() doesn't actually return a MutableList. It returns a specialized internal Kotlin class that is read-only. You can't "cast" your way into mutability if the underlying object doesn't support it. It's not just a type-system trick; it's a fundamental difference in the object being created.
Read-Only Interfaces vs. Immutable Data
This is where things get a bit nuanced. In Kotlin, List is read-only, not necessarily immutable. This is a distinction that trips people up constantly.
A MutableList is a List, but a List is not always a MutableList. When you define a variable as List<T>, you are telling the compiler: "I don't care how this list was made; I just promise not to try and change it."
I usually tell my juniors to think of it like a "view." A List is a window through which you can see the data, but you can't reach through the glass to move things around. If you actually need to modify the data, you have two real options:
- Create a mutable copy: Use
.toMutableList(). This creates a brand newArrayListcontaining the elements of the original list. Now you can add or remove items without crashing, and without affecting the original source list. - Start with a MutableList: Use
mutableListOf()from the beginning if you know the collection needs to change.
Here is the right way to handle that session manager logic:
val activeUsers = listOf("Alice", "Bob")
fun getActiveUsers(): List<String> = activeUsers
fun main() {
val users = getActiveUsers()
// Create a new mutable copy instead of casting
val mutableUsers = users.toMutableList()
mutableUsers.add("Charlie")
println(mutableUsers) // ["Alice", "Bob", "Charlie"]
println(activeUsers) // ["Alice", "Bob"] - The original stays safe!
}
I personally prefer this approach because it prevents "spooky action at a distance." If you just cast a list and modify it, you might accidentally change the state of a list being used by five other classes in your app, leading to bugs that are a nightmare to debug. By forcing a copy via toMutableList(), you're being explicit about your intentions.
📋 Practical Task
Refactoring the Inventory System Crash
You've been handed a piece of code for a warehouse inventory system. The previous developer tried to be "clever" by casting the read-only inventory list to a MutableList to add a new item. As a result, the system crashes every time a new product is registered.
Your Task: Fix the registerProduct function so that it successfully adds the new product to a list without causing a ClassCastException, ensuring the original masterInventory remains unchanged.
val masterInventory = listOf("Screwdriver", "Hammer", "Drill")
fun getInventory(): List<String> {
return masterInventory
}
fun registerProduct(newProduct: String) {
val currentInventory = getInventory()
// FIX THIS LINE:
val editableInventory = currentInventory as MutableList<String>
editableInventory.add(newProduct)
println("Updated inventory: $editableInventory")
}
fun main() {
registerProduct("Wrench")
}
There are no comments for now.