Question
In Java, you can declare a variable using the List interface and still call methods such as add() and remove() when the actual object is an ArrayList.
public class TempClass {
List<Integer> myList = null;
void doSomething() {
myList = new ArrayList<>();
myList.add(10);
myList.remove(10);
}
}
When translating this directly to Kotlin, the equivalent code does not work:
class TempClass {
var myList: List<Int>? = null
fun doSomething() {
myList = ArrayList<Int>()
myList!!.add(10)
myList!!.remove(10)
}
}
Kotlin reports that add() and remove() do not exist on List.
A workaround is to cast the value to ArrayList, but that feels incorrect and removes the benefit of programming to the abstract type:
class TempClass {
var myList: List<Int>? = null
fun doSomething() {
myList = ArrayList<Int>()
(myList!! as ArrayList<Int>).add(10)
(myList!! as ArrayList<Int>).remove(10)
}
}
Why does Kotlin behave differently from Java here, and how can you use a list or map in Kotlin without needing unsafe casts when you want to modify it?
Short Answer
By the end of this page, you will understand why Kotlin separates read-only collection types like List and Map from mutable collection types like MutableList and MutableMap. You will learn when to use each one, how to write modifiable collections correctly, and why casting is usually the wrong fix.
Concept
Kotlin intentionally separates read-only and mutable collection interfaces.
In Java, List includes both reading and writing operations:
- read:
get(), iteration,size - write:
add(),remove(),clear()
In Kotlin, these responsibilities are split:
List<T>: read-only viewMutableList<T>: can be changedMap<K, V>: read-only viewMutableMap<K, V>: can be changed
That is why this does not compile:
var myList: List<Int>? = null
myList!!.add(10) // Error
The variable type is List<Int>, and List does not promise mutation methods.
Why Kotlin does this
This design makes code safer and clearer.
Mental Model
Think of Kotlin collections like access passes to a room.
Listis a visitor pass: you can enter, look around, and count things, but you cannot move furniture.MutableListis a staff pass: you can enter and rearrange the room.
The actual room might be the same ArrayList, but your pass determines what actions are allowed.
So if your variable is typed as List, Kotlin says: "You only asked for a visitor pass, so you cannot use staff-only actions like add() or remove()."
Syntax and Examples
Core syntax
Read-only list
val numbers: List<Int> = listOf(1, 2, 3)
println(numbers[0])
println(numbers.size)
You can read values, but not change the list.
Mutable list
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
numbers.add(4)
numbers.remove(2)
println(numbers)
You can both read and modify it.
Read-only map
val ages: Map<String, Int> = mapOf("Ana" to 25, "Ben" to 30)
println(ages["Ana"])
Mutable map
val ages: MutableMap<String, Int> = mutableMapOf("Ana" to 25)
ages["Ben"] = 30
ages.put(, )
println(ages)
Step by Step Execution
Consider this example:
fun main() {
val items: MutableList<String> = mutableListOf()
items.add("apple")
items.add("banana")
items.remove("apple")
println(items)
}
Step by step
1. Create an empty mutable list
val items: MutableList<String> = mutableListOf()
itemsis a variable that refers to a mutable list.- The list starts empty:
[].
2. Add apple
items.add("apple")
- The list becomes:
["apple"].
3. Add banana
items.add("banana")
- The list becomes:
["apple", "banana"].
Real World Use Cases
1. Passing safe read-only data to functions
A service may return a List<User> so callers can read the users without modifying the internal collection.
fun getUsers(): List<User> = users
2. Building collections during processing
When collecting results step by step, use MutableList.
val errors = mutableListOf<String>()
if (name.isBlank()) errors.add("Name is required")
if (email.isBlank()) errors.add("Email is required")
3. Updating configuration or caches
A cache often needs a MutableMap because entries are added and replaced over time.
val cache = mutableMapOf<String, String>()
cache["theme"] = "dark"
4. API response transformation
You might read from a List returned by an API and build a new mutable result list.
result = mutableListOf<String>()
(user apiUsers) {
result.add(user.name)
}
Real Codebase Usage
In real Kotlin projects, developers usually follow this rule:
- use
MutableListandMutableMaponly where mutation is required - expose
ListandMapwhenever possible
Common patterns
1. Private mutable, public read-only
class Cart {
private val _products = mutableListOf<String>()
val products: List<String> get() = _products
fun addProduct(product: String) {
_products.add(product)
}
}
This prevents outside code from changing the collection directly.
2. Build mutable, return read-only
fun loadNames(): List<String> {
val result = mutableListOf<String>()
result.add("Ava")
result.add("Leo")
return result
}
The function uses mutation internally but returns a read-only type.
3. Validation and accumulation
Common Mistakes
1. Declaring List when you need MutableList
Broken code:
val items: List<Int> = mutableListOf()
items.add(1) // Error
Fix:
val items: MutableList<Int> = mutableListOf()
items.add(1)
2. Casting just to force mutation
Broken code:
val items: List<Int> = mutableListOf(1, 2, 3)
(items as ArrayList<Int>).add(4)
Why this is bad:
- unsafe
- tightly couples your code to a specific implementation
- may fail at runtime if the object is not actually an
ArrayList
Better:
val items: MutableList<Int> = mutableListOf(1, , )
items.add()
Comparisons
| Concept | Kotlin Type | Can Read? | Can Modify? | Typical Use |
|---|---|---|---|---|
| Read-only list | List<T> | Yes | No | Expose or consume data safely |
| Mutable list | MutableList<T> | Yes | Yes | Build or update collections |
| Read-only map | Map<K, V> | Yes | No | Lookup data without mutation |
| Mutable map | MutableMap<K, V> | Yes | Yes | Caches, counters, configuration |
Cheat Sheet
// Read-only list
val a: List<Int> = listOf(1, 2, 3)
// Mutable list
val b: MutableList<Int> = mutableListOf(1, 2, 3)
b.add(4)
b.remove(2)
// Read-only map
val c: Map<String, Int> = mapOf("x" to 1)
// Mutable map
val d: MutableMap<String, Int> = mutableMapOf("x" to 1)
d["y"] = 2
d.put("z", 3)
Rules to remember
- Use
Listwhen you only need to read. - Use
MutableListwhen you needadd,remove, orclear. - Use
Mapwhen you only need lookup. - Use
MutableMapwhen you need , assignment, or removal.
FAQ
Why does Kotlin List not have add()?
Because Kotlin separates read-only and mutable collection interfaces. List is for reading, while MutableList is for modification.
How do I add items to a list in Kotlin?
Declare the variable as MutableList<T> and create it with mutableListOf() or ArrayList().
val items = mutableListOf<Int>()
items.add(1)
Why does Kotlin Map not have put()?
For the same reason. Map is read-only, and MutableMap is the type that supports put(), assignment, and removal.
Can I cast List to ArrayList in Kotlin?
You can, but it is usually a bad idea. It is unsafe and depends on the actual implementation. Prefer declaring MutableList from the start.
Mini Project
Description
Create a small inventory tracker for a shop. This project demonstrates when to use MutableList internally to update data and when to expose a read-only List to the rest of the program. It mirrors how Kotlin collections are commonly used in real applications.
Goal
Build a class that stores product names, allows products to be added and removed, and exposes the current inventory safely as a read-only list.
Requirements
- Create a class named
Inventory. - Store products internally in a mutable collection.
- Expose the product list as a read-only
List<String>. - Add a function to add a product.
- Add a function to remove a product.
- Print the inventory before and after updates.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.