skip to content

Predict and explain the result types of these on a Set/Map: `setOf(3,1,2).map { it }`, `setOf(1,2,3).filter { it > 1 }`, and `mapOf(1 to "a").filter { it.key == 1 }`. Why do they differ?

level: middleimportance: should knowfreq 45%

answer

  1. Iterable.map and Iterable.filter both -> List
  2. Set in, List out unless you convert
  3. Map.filter / filterKeys / filterValues / mapValues -> Map
  4. Map.map (entry transform) -> List
  5. mapKeys collisions overwrite silently

basics

~20 s

map on a Set gives a List. filter on a Set gives a List too. filter on a Map gives a Map. The result type depends on which operator and which receiver, because transforming can change duplicates and ordering.

solid answer

~40 s

`setOf(3,1,2).map { it }` -> `List<Int>`: `map` is declared on `Iterable` and a transform can yield duplicates plus an iteration order, so the only safe general result is a `List`. `setOf(1,2,3).filter { it > 1 }` -> `List<Int>` too: `filter` is also an `Iterable` extension and returns a `List` regardless of receiver. `mapOf(1 to "a").filter { it.key == 1 }` -> `Map<Int,String>`: there is a *specific* `Map.filter` overload returning a `Map`, since filtering entries preserves the key/value structure. The pattern: `Iterable` transform/filter operators return `List`; to retain a `Set` use `mapTo(mutableSetOf())`/`.toSet()`; `Map` has dedicated entry-aware operators (`filterKeys`, `filterValues`, `mapValues`) that keep `Map`.

code

kotlin · 5 lines
kotlin
val a: List<Int> = setOf(3, 1, 2).map { it }            // List, order/dupes possible
val b: List<Int> = setOf(1, 2, 3).filter { it > 1 }     // List(2, 3)
val c: Map<Int, String> = mapOf(1 to "a").filter { it.key == 1 } // Map
val d: Map<Int, String> = mapOf(1 to "a", 2 to "b").mapValues { it.value.uppercase() }
val e: Set<Int> = setOf(1, 2, 3).mapTo(mutableSetOf()) { it * 2 } // Set

go deeper

for a junior

May guess the result keeps the input type; learns map/filter on Set give List.

for a middle

Correctly predicts List vs Map results and knows the Map-specific operators.

for a senior

Explains why semantics force these types and uses mapTo/filterValues deliberately; aware of mapKeys collisions.

for a principal

Codifies guidance to avoid accidental type widening and silent data loss in shared collection-processing code.

## Why result types aren't just 'the same as the input' Kotlin's operators are extensions on broad receivers, and the operation's semantics decide what shape can be guaranteed. ### `setOf(3,1,2).map { it }` => `List<Int>` `map` is `Iterable<T>.map(transform): List<R>`. A `Set` is an `Iterable`, so this overload applies. The transform could map distinct inputs to equal outputs (e.g. `map { it % 2 }` collapses values) and `map` also commits to an order — neither is compatible with `Set` guarantees, so the result is a `List` with possible duplicates. ### `setOf(1,2,3).filter { it > 1 }` => `List<Int>` `filter` is `Iterable<T>.filter(predicate): List<T>`. Even though filtering a set never *adds* duplicates, the stdlib still returns a `List` for uniformity across all `Iterable`s. So you get `List(2, 3)`, not a `Set`. ### `mapOf(1 to "a").filter { it.key == 1 }` => `Map<Int, String>` `Map<K,V>` has its own `filter(predicate: (Map.Entry<K,V>) -> Boolean): Map<K,V>`. Filtering entries can't break key uniqueness, so the structure is preserved and the result stays a `Map`. ## Keeping the type you want ```kotlin val keptSet: Set<Int> = setOf(1, 2, 3).mapTo(mutableSetOf()) { it * 2 } // Set val keptSet2 = setOf(1, 2, 3).map { it * 2 }.toSet() // Set (extra pass) val onlyVals: Map<Int, String> = mapOf(1 to "a", 2 to "b").filterValues { it == "a" } val upper = mapOf(1 to "a").mapValues { it.value.uppercase() } // Map<Int,String> ``` ## Map-specific operators that preserve Map - `filterKeys { }`, `filterValues { }`, `filter { entry -> }` -> `Map`. - `mapValues { }` -> `Map` (keys preserved). - `mapKeys { }` -> `Map` (but colliding new keys silently overwrite!). - `map { entry -> }` -> `List` (transforms entries into arbitrary values, so it degrades to a List). ## Mental model - `Iterable.map` / `Iterable.filter` -> `List`. - `Map.filter` / `filterKeys` / `filterValues` / `mapValues` / `mapKeys` -> `Map`. - `Map.map` -> `List`. - Want a `Set`? Convert explicitly.

  • How do you map a Set and keep it a Set in one pass?
    Use mapTo(mutableSetOf()) { transform } which writes results into a destination Set, deduplicating, without an intermediate List. Or chain .map { }.toSet() at the cost of an extra pass.
  • What surprising bug can mapKeys cause?
    If the key transform produces the same key for two entries, later entries silently overwrite earlier ones, shrinking the map. Validate uniqueness or use a grouping approach if collisions matter.

saying these in an interview costs you the question

  • Assuming map on a Set returns a Set
  • Assuming filter on a Set returns a Set
  • Thinking Map.map returns a Map
  • Not knowing filterValues/mapValues preserve Map
  • Unaware mapKeys can silently drop entries on collision

context