Mutable vs Immutable Collections in Kotlin

In this guide, we’ll explore the differences, benefits, and use cases of mutable and immutable collections in Kotlin.

Kotlin’s collection framework provides both mutable and immutable variants of collections, offering developers flexibility while maintaining code safety. In this comprehensive guide, we’ll explore the differences, benefits, and use cases of both collection types.

Understanding Mutability in Kotlin Collections

Kotlin makes a clear distinction between mutable and immutable collections through its type system. This distinction helps developers make better decisions about data modification and access patterns.

Basic Differences

Let’s start with the fundamental differences:

// Immutable Collections
val immutableList = listOf(1, 2, 3)
val immutableSet = setOf(1, 2, 3)
val immutableMap = mapOf("one" to 1, "two" to 2)

// Mutable Collections
val mutableList = mutableListOf(1, 2, 3)
val mutableSet = mutableSetOf(1, 2, 3)
val mutableMap = mutableMapOf("one" to 1, "two" to 2)

Immutable Collections

Characteristics and Benefits

  1. Thread Safety:
val sharedData = listOf(1, 2, 3, 4, 5)
// Safe to share across threads as it cannot be modified
  1. Predictable Behavior:
fun processItems(items: List<String>) {
    // We can be confident that items won't be modified
    items.forEach { item ->
        println(item)
    }
}
  1. Functional Programming Support:
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val filtered = numbers.filter { it % 2 == 0 }

Common Operations

fun demonstrateImmutableOperations() {
    val original = listOf(1, 2, 3)
    
    // Creating new collections from operations
    val added = original + 4
    val removed = original - 2
    val combined = original + listOf(4, 5, 6)
    
    // Transformations create new collections
    val mapped = original.map { it * 2 }
    val filtered = original.filter { it > 1 }
}

Mutable Collections

Characteristics and Benefits

  1. In-place Modifications:
val numbers = mutableListOf(1, 2, 3)
numbers.add(4)
numbers.remove(2)
numbers[0] = 10
  1. Performance Benefits:
fun buildLargeList(): List<Int> {
    val result = mutableListOf<Int>()
    for (i in 1..10000) {
        result.add(i)  // More efficient than creating new immutable lists
    }
    return result
}
  1. Dynamic Content Management:
class ShoppingCart {
    private val items = mutableListOf<Item>()
    
    fun addItem(item: Item) {
        items.add(item)
    }
    
    fun removeItem(item: Item) {
        items.remove(item)
    }
}

Common Operations

fun demonstrateMutableOperations() {
    val numbers = mutableListOf(1, 2, 3)
    
    // Modifying the collection
    numbers.add(4)
    numbers.addAll(listOf(5, 6))
    numbers.removeAt(0)
    numbers.clear()
    
    // Bulk modifications
    numbers.addAll(1..5)
    numbers.removeAll { it % 2 == 0 }
    numbers.retainAll { it < 4 }
}

Making the Right Choice

When to Use Immutable Collections

  1. For API Design:
class UserRepository {
    // Return immutable list to prevent modifications
    fun getAllUsers(): List<User> {
        return users.toList()  // Create immutable copy
    }
}
  1. For Thread Safety:
class SharedResource {
    private val data = listOf(1, 2, 3, 4, 5)
    
    fun getData(): List<Int> {
        return data  // Safe to share across threads
    }
}
  1. For Function Parameters:
fun processItems(items: List<String>) {
    // Using immutable list ensures items won't be modified
    items.forEach { item ->
        println(item)
    }
}

When to Use Mutable Collections

  1. For Building Collections:
fun buildCollection(): List<String> {
    val result = mutableListOf<String>()
    // Add items efficiently
    result.add("Item 1")
    result.add("Item 2")
    return result.toList()  // Convert to immutable for return
}
  1. For Caching:
class Cache<K, V> {
    private val storage = mutableMapOf<K, V>()
    
    fun put(key: K, value: V) {
        storage[key] = value
    }
    
    fun get(key: K): V? = storage[key]
}
  1. For Internal State:
class DataProcessor {
    private val processedItems = mutableSetOf<String>()
    
    fun process(item: String) {
        if (item !in processedItems) {
            // Process item
            processedItems.add(item)
        }
    }
}

Best Practices and Patterns

Converting Between Mutable and Immutable

fun demonstrateConversion() {
    // Converting mutable to immutable
    val mutableList = mutableListOf(1, 2, 3)
    val immutableList: List<Int> = mutableList.toList()
    
    // Creating mutable copy of immutable collection
    val immutable = listOf(1, 2, 3)
    val mutable = immutable.toMutableList()
}

Defensive Copying

class SafeContainer<T> {
    private val items = mutableListOf<T>()
    
    fun addItem(item: T) {
        items.add(item)
    }
    
    // Return defensive copy
    fun getItems(): List<T> {
        return items.toList()
    }
}

Thread-Safe Collection Usage

class ThreadSafeRepository {
    private val _items = mutableListOf<String>()
    private val lock = Any()
    
    fun addItem(item: String) {
        synchronized(lock) {
            _items.add(item)
        }
    }
    
    fun getItems(): List<String> {
        synchronized(lock) {
            return _items.toList()
        }
    }
}

Performance Considerations

fun performanceExample() {
    // Efficient for building
    val mutableList = mutableListOf<Int>()
    repeat(1000) {
        mutableList.add(it)
    }
    
    // Less efficient, creates multiple lists
    var immutableList = listOf<Int>()
    repeat(1000) {
        immutableList = immutableList + it
    }
}

Conclusion

The choice between mutable and immutable collections in Kotlin depends on various factors:

  1. Immutable Collections:

    • Provide thread safety
    • Ensure predictable behavior
    • Support functional programming patterns
    • Ideal for public APIs and shared data
  2. Mutable Collections:

    • Allow in-place modifications
    • More efficient for building collections
    • Suitable for internal state management
    • Better for frequently changing data

Best practices to remember:

  • Use immutable collections by default
  • Convert to mutable only when necessary
  • Implement defensive copying when exposing collections
  • Consider thread safety implications
  • Use the appropriate collection type for your use case

By understanding these differences and following these guidelines, you can make informed decisions about collection mutability in your Kotlin code, leading to more maintainable and robust applications.


Last modified 20.02.2025: new kotlin and mint content (93a1000)