copy() performs a shallow copy. What problems can this cause when a data class holds mutable or nested objects, and how do you update nested immutable state correctly?
answer
- Shallow = nested references shared
- Mutable nested state leaks across copies (aliasing)
- Nest copy(): outer.copy(inner = inner.copy(...))
- Replace collections, don't mutate them
- Deep nesting -> Arrow Optics lenses / helpers
basics
~20 scopy() only copies the top object. Anything it points to (lists, other objects) is shared with the original. If that shared thing is mutable, changing it affects both. To update nested data, copy() the inner object too and slot it in.
solid answer
~40 scopy() duplicates only the immediate object; reference-typed properties are copied by reference, so the new and old instances share the same nested object. If that nested object is mutable (e.g., a MutableList or a class with var fields), a mutation is visible through both instances — a classic aliasing bug that undermines perceived immutability. The fix is to keep nested types immutable and update them via their own copy(), producing a new outer object whose changed branch points to a new inner object: outer.copy(inner = outer.inner.copy(x = ...)). For collections, replace with a new immutable list (e.g., list.map { ... } or list + element) rather than mutating in place. Deep nesting makes this verbose; teams reach for optics-style libraries (Arrow Optics lenses) or extract update helpers to manage it.
code
kotlin · 8 linesdata class Address(val city: String)
data class User(val name: String, val address: Address)
val a = User("Ada", Address("London"))
val b = a.copy(address = a.address.copy(city = "Paris"))
println(a.address.city) // London (unchanged)
println(b.address.city) // Parisgo deeper
Recognizes copy() returns a new object but may not realize nested references are shared.
Explains shallow-copy aliasing and fixes it with nested copy() and immutable collections.
Designs nested state to stay immutable, reasons about deep-update cost, and reaches for lenses/helpers.
Weighs immutability strategy across the codebase: state shape, library choice (Arrow Optics), and performance of structural sharing.
## Shallow vs deep copy A **shallow copy** duplicates the object itself but copies each property by value as it stands. For reference types, the *value* is the reference (the pointer), so both the original and the copy point at the **same** nested object. A **deep copy** would recursively duplicate nested objects too; `copy()` does NOT do this. ```kotlin data class Address(var city: String) // mutable on purpose to show the bug data class User(val name: String, val address: Address) val a = User("Ada", Address("London")) val b = a.copy(name = "Bob") b.address.city = "Paris" println(a.address.city) // "Paris" — leaked into a too! ``` Because `a` and `b` share the same `Address`, mutating it through `b` is visible through `a`. This **aliasing** breaks the illusion of independent immutable values. ## The correct pattern: immutable nested types + nested copy() Keep nested types immutable (all `val`) and update by copying the inner object and slotting it in: ```kotlin data class Address(val city: String) data class User(val name: String, val address: Address) val a = User("Ada", Address("London")) val b = a.copy(address = a.address.copy(city = "Paris")) // a.address.city is still "London"; b has a brand-new Address ``` Each changed level produces a new object; unchanged branches are still shared, which is safe because they are immutable. ## Collections Prefer **read-only** collection types (`List`, `Map`) and produce new ones instead of mutating: ```kotlin data class Cart(val items: List<String>) val c2 = c1.copy(items = c1.items + "apple") // new list val c3 = c1.copy(items = c1.items.map { it.uppercase() }) ``` Using a `MutableList` property and calling `.add()` mutates the shared list and reintroduces the aliasing bug. ## Deep nesting cost Updating something five levels deep means nesting five `copy()` calls — verbose and error-prone. Mitigations: extract update helper functions, model state to minimize depth, or use **lens** libraries such as **Arrow Optics** that generate focused updaters. ## Summary - `copy()` is shallow; nested references are shared. - Mutable nested state + shallow copy = aliasing bugs. - Fix: immutable nested types, update with nested `copy()`, replace collections with new immutable ones.
- How do you update a value three levels deep immutably?Nest copy() calls at each level, or use a lens (Arrow Optics) to avoid the boilerplate of repeated copy() nesting.
- Why prefer List over MutableList in a data class property?A read-only List forces a new collection on change, preserving immutability; a MutableList can be mutated in place and shared across copies, reintroducing aliasing.
Photocopying a folder's cover page but stapling the same original documents to both copies — edit one document and both folders show the change.
saying these in an interview costs you the question
- Claiming copy() deep-copies nested objects
- Using MutableList properties then mutating after copy()
- Not recognizing the aliasing bug from shared references
- Saying you must manually new-up the whole tree instead of nesting copy()