Skip to Content
Course content

85: MutableList vs List Semantics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 new ArrayList containing 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")
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.