ProgrammingMiddle Android Developer

How is immutability implemented in Kotlin for collections and objects, and how does it differ in practice from Java?

Pass interviews with Hintsage AI assistant

Answer

Background:

In Java, immutability of collections is achieved through wrappers like Collections.unmodifiableList() and designing immutable classes manually (final fields, absence of setters). In Android and server-side development, this became cumbersome and inconvenient, while thread safety and data predictability were compromised.

Problem:

Java collections are not safe by default; you can always obtain a reference and modify the object. This complicates the control over mutability, especially in multithreaded environments, and leads to hard-to-trace bugs.

Solution:

Kotlin was initially designed with a separation of read-only and mutable collections, and creating truly immutable objects became easier with val, data class, and List/Set/Map without the mutable prefix. Moreover, copying through copy() methods was introduced.

Code example:

val nums: List<Int> = listOf(1, 2, 3) // immutable list // nums.add(4) // compilation error val user = User(name = "Alex", age = 30) val updated = user.copy(age = 31) // Example with immutable map val state: Map<String, Int> = mapOf("a" to 1, "b" to 2)

Key features:

  • Compilation will throw an error if trying to modify a read-only collection
  • In Kotlin, val guarantees immutability of the reference but not necessarily of the object
  • For truly immutable — use only read-only interfaces and prohibit changing internal state

Trick questions.

Does val guarantee absolute immutability of the nested object?

No! val only guarantees that the variable cannot point to another object, but the fields of the object can be changed (if the internal properties are var).

Can a mutable object be obtained from a read-only collection?

Yes — if the collection contains a mutable object, its state can be changed despite the read-only interface of the collection.

data class User(var name: String) val users: List<User> = listOf(User("Vasya")) users[0].name = "Petya" // allowed

What happens when casting List<Int> to MutableList<Int> using as?

It will compile, but it will lead to ClassCastException at runtime if the original type was a read-only interface.

Common mistakes and anti-patterns

  • Assuming val is an absolute guarantee of object immutability
  • Manipulating references to mutable objects within "immutable" collections
  • Casting read-only collections to mutable ones

Real-life example

Negative case

An Android application stores state in val List<User], considering it immutable. One of the developers modifies user.name. In another fragment, the updated list unexpectedly displays inconsistent data — the bug appears in the release.

Pros:

  • Fast operations with collections

Cons:

  • Loss of control over mutations
  • Hidden mutability

Positive case

Using immutable data class only with val fields, filtering access to nested objects, updating state through copy().

Pros:

  • High predictability and thread safety
  • Absence of unexpected side effects

Cons:

  • Sometimes requires more allocations and copies
  • Not all third-party libraries provide truly immutable objects