Kotlin / Language foundations and explicit state / Lesson 4
Choose collections and transform them deliberately
Use lists, sets, maps, read-only views, filtering, and mapping while preserving order and ownership meaning.
On this page
Lesson goals
- Choose a collection from ordering, uniqueness, and lookup requirements.
- Distinguish a read-only interface, a shallow snapshot, and shared mutable elements.
Select the data relationship before its methods
A List preserves an indexed sequence and permits duplicates. A Set models uniqueness, with iteration order depending on the chosen implementation or factory contract. A Map associates unique keys with values and is not itself a Collection subtype in Kotlin. Choose based on the question: a registration order is a list, a unique set of selected workshop identifiers is a set, and a lookup from identifier to description is a map. Do not remove duplicates casually when repeated values carry business meaning, such as several seats belonging to one reservation.
Keep predicates and transformations separate
filter retains elements satisfying a Boolean predicate; map transforms each element into a result. For an eager list pipeline, each operation normally constructs its result collection, so clarity and intermediate cost both matter. The sample filters available workshop capacities and converts them to labels without changing the input list. A lambda’s it refers to its single implicit parameter when a name is omitted. Use explicit parameter names when nested lambdas or domain meaning make it ambiguous. Avoid side effects inside transformations that readers expect to describe data flow.
fun main() {
val capacities = listOf(0, 3, 1, 0)
val labels = capacities.filter { seats -> seats > 0 }
.map { seats -> "available:$seats" }
println(labels.joinToString("|"))
println(capacities.size)
}
available:3|available:1
4
Expected lines are available:3|available:1 and 4. Filtering preserves the relative order of retained list elements, and the original list still has four entries. This program was not compiled here.
A read-only view can still observe someone else’s mutation
List exposes read operations, while MutableList adds mutation operations. Assigning a mutable list to a List reference narrows the available interface but does not copy or freeze the object. Another reference can still change it. A toList call creates a list snapshot of the element references, not a deep copy of the elements themselves. If those elements are mutable objects, both lists can still observe their changes. Decide whether a function promises a stable snapshot, a live view, or exclusive ownership and document that promise at the boundary.
Map absence and duplicate keys have explicit consequences
A map lookup with brackets returns a nullable value because the key may be absent. If the map’s value type is itself nullable, a null lookup alone cannot distinguish a present null from a missing key; containsKey answers the presence question. Building a map with duplicate keys replaces the earlier associated value according to the factory or operation contract rather than preserving both as a list would. If duplicate business identifiers are invalid, validate them instead of letting map construction silently decide which record survives.
fun main() {
val mutable = mutableListOf("ws-1")
val view: List<String> = mutable
val snapshot = mutable.toList()
mutable.add("ws-2")
println(view.size)
println(snapshot.size)
val titles = mapOf("ws-1" to "Basics")
println(titles["missing"] ?: "not found")
}
2
1
not found
Expected output is 2, 1, not found. String elements are used so element-level mutation does not complicate the shallow-copy example. No runtime execution is claimed.
Handle empty and oversized inputs intentionally
An empty collection is a normal value for many operations: filter and map produce empty results, and sum of an empty integer collection is zero. Operations such as first can instead fail when no element exists; firstOrNull preserves that possibility. Do not convert every missing item into a default identifier that could refer to a real record. For large data, eager intermediate collections may matter, but a sequence is not automatically faster and should be introduced with its evaluation semantics. Choose correctness and bounded workload before optimizing pipeline syntax.
Common errors and fixes
A supposedly fixed List changes after return.
- Why it happens
- A shared MutableList was exposed through a read-only reference.
- How to fix it
- Return an appropriate snapshot or clarify live-view ownership.
- Prevent it next time
- Test mutation through the original alias.
Duplicate identifiers disappear during map construction.
- Why it happens
- A unique-key structure was used without duplicate validation.
- How to fix it
- Reject duplicates or group values explicitly according to the domain.
- Prevent it next time
- Include duplicate-key input in boundary examples.
first throws on an empty filtered result.
- Why it happens
- The algorithm assumed a match always exists.
- How to fix it
- Use firstOrNull and handle absence or establish a justified invariant.
- Prevent it next time
- Check no-match cases alongside ordinary input.
Check your understanding
1. Does toList protect mutable objects stored inside the list?
Show worked solution
No. It copies the list structure’s element references, so both lists may refer to the same mutable objects. Use immutable element models, carefully defined deep copying, or an ownership discipline if stable contents are required. Do not describe a shallow snapshot as a complete freeze.
2. A registration list contains the same learner twice for two seats. Should toSet be used to clean it?
Show worked solution
Only if the domain declares those entries accidental duplicates. If repetition represents quantity, converting to a set loses information. Prefer a reservation model with an explicit seat count or preserve the list semantics. Data cleaning must follow meaning, not the availability of a convenient collection operation.
Key takeaways
- Collection choice encodes ordering, uniqueness, and lookup meaning.
- Read-only, copied, and deeply immutable are different promises.