skip to content

Kotlin's collection operators like map, filter, and groupBy aren't methods on the collection classes. Where do they come from, and what does that imply?

level: middleimportance: must knowfreq 80%

answer

  1. Operators = stdlib extensions on Iterable/Collection/Map
  2. Static dispatch, resolved by import, not virtual
  3. Eager on collections: a new List per step
  4. map on a Set returns a List
  5. asSequence() for lazy, allocation-free chains

basics

~20 s

They are extension functions defined in the Kotlin standard library, not members of the collection classes. They work on any Iterable or Collection, so the same operators apply to lists, sets, and even Java collections.

solid answer

~40 s

Operators like `map`, `filter`, `flatMap`, `groupBy`, `associate`, `partition`, `fold`, and `sortedBy` are **extension functions** declared on `Iterable<T>` (or `Collection`, `Array`, `Map`) in `kotlin.collections`. Because they're extensions, they're available on every conforming type, including Java collections seen through interop, without those types implementing anything new. On collections they are **eager**: each operator allocates and returns a new `List` (or `Set`/`Map`), so a chain `list.filter{}.map{}` creates an intermediate list per step. Extensions are resolved statically by the compiler from imports, dispatch on the static (declared) receiver type, and can't be overridden polymorphically. Many return `List` even from a `Set` input because the result ordering/duplicates semantics differ. The lazy alternative is `asSequence()`, which swaps these for sequence extensions that fuse without intermediates.

code

kotlin · 9 lines
kotlin
// Each step allocates an intermediate list (eager):
val names = listOf("ann", "bob", "amy")
val byInitial = names
    .filter { it.length == 3 }   // List("ann","bob","amy")
    .groupBy { it.first() }      // Map('a' -> [ann, amy], 'b' -> [bob])
println(byInitial)

// map on a Set yields a List, not a Set:
val s: List<Int> = setOf(1, 2, 3).map { it * 2 }

go deeper

for a junior

Knows map/filter exist and produce a new collection but may not know they're extensions.

for a middle

Identifies them as stdlib extension functions on Iterable, knows eager evaluation and that map on a Set returns a List.

for a senior

Explains static dispatch, intermediate-list cost, return-type subtleties, and when to switch to sequences.

for a principal

Weighs API design of extensions vs members, interop universality, and performance/allocation trade-offs across large pipelines.

## Extension functions, not members An **extension function** is a function declared *outside* a class but callable with dot syntax as if it were a member, e.g. `fun <T> Iterable<T>.map(transform: (T) -> R): List<R>`. Kotlin's huge operator chain lives in the standard library package `kotlin.collections`, declared on receivers like `Iterable<T>`, `Collection<T>`, `Array<T>`, `Map<K, V>`, and `CharSequence`. Consequences: - **Universality**: any `Iterable` gets them for free — `listOf`, `setOf`, a Java `ArrayList`, your own `Iterable` implementation. - **Static dispatch**: extensions are resolved at compile time by the *declared* type of the receiver, not the runtime type. They are NOT virtual and cannot be overridden. `import` brings them into scope. - **No subclass coupling**: you can add operators to types you don't own (Java collections) without modifying them. ## They are eager on collections Each operator returns a brand-new collection immediately: ```kotlin val result = listOf(1, 2, 3, 4) .filter { it % 2 == 0 } // allocates List [2, 4] .map { it * it } // allocates List [4, 16] ``` For an N-step chain over collections you get up to N intermediate lists, each fully materialized before the next step runs (horizontal evaluation: the whole collection passes through step 1, then the whole result through step 2). ## Common families - **Transform**: `map`, `mapIndexed`, `flatMap`, `mapNotNull`. - **Filter**: `filter`, `filterNot`, `filterNotNull`, `filterIsInstance`, `partition`. - **Group/associate**: `groupBy`, `groupingBy`, `associate`, `associateBy`, `associateWith`. - **Aggregate**: `fold`, `reduce`, `sumOf`, `count`, `maxByOrNull`, `minOf`. - **Order**: `sorted`, `sortedBy`, `sortedWith`, `reversed`, `distinct`. ## Return-type subtleties Many operators **return `List`** even from a `Set` input (e.g. `setOf(3,1,2).map { it }` is a `List`), because mapping can introduce duplicates and an order. To keep a set use `mapTo(mutableSetOf()) { }` or `.toSet()`. `filter` on a `Map` returns a `Map`, but `map` on a `Map` returns a `List` of whatever the lambda yields. ## When eager hurts Large inputs, expensive transforms, short-circuiting (`first`, `any`), or long chains favor `asSequence()` to avoid intermediate allocations and to stop early.

  • Why does `setOf(1,2,3).map { it }` return a List instead of a Set?
    map is declared on Iterable and its transform may produce duplicates and an ordering, so the only safe general result is a List. Use mapTo(mutableSetOf()) or .toSet() to get a Set.
  • Can an extension operator be overridden by a subclass for polymorphic behavior?
    No. Extensions are dispatched statically by the declared receiver type at compile time; they aren't virtual members and can't be overridden.

Extension operators are like power tools you can clip onto any standard socket (Iterable) — they aren't built into the appliance, but any compatible device can use them.

saying these in an interview costs you the question

  • Saying map/filter are methods defined on ArrayList/List
  • Believing extension functions dispatch dynamically/virtually
  • Assuming a chain over a collection is lazy
  • Expecting map on a Set to return a Set
  • Thinking you must subclass to add operators to Java collections

context