跪拜 Guibai
← All articles
Android · Kotlin

Kotlin's Collection Operators Are Wrappers Around a Single Delegate Pattern

By RockByte ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Understanding the delegate pattern means you can use filterTo, mapTo, and friends directly to reuse destination collections and avoid unnecessary intermediate allocations — a common need in performance-sensitive Android and server-side Kotlin code. The inline mechanism also clarifies why these operators carry zero abstraction penalty and work seamlessly inside coroutines and Compose.

Summary

Every major Kotlin collection operator — map, flatMap, groupBy, zip, and filter — shares a consistent two-layer design. The public function is a thin wrapper that creates an optimized destination collection (an ArrayList sized correctly when the source is a Collection, defaulting to 10 otherwise) and immediately hands off to a *To function. The *To variant runs the actual loop, applying the lambda and populating the mutable destination.

This delegation pattern means the standard library avoids duplicating iteration logic. mapTo, flatMapTo, groupByTo, filterTo, and zip's transform overload each contain the sole implementation; the convenience functions just supply the right starting container. For zip, the simple infix version is itself a delegation to the transform overload with a default Pair-construction lambda.

Performance hinges on two details: the inline modifier eliminates lambda object allocations at every call site, and the collectionSizeOrDefault check lets map and zip pre-allocate ArrayLists to their exact final size when the source is a Collection. The inline keyword also explains why suspend and @Composable calls work inside these lambdas — the lambda body is copied into the caller's context, inheriting its capabilities.

Takeaways
map, flatMap, groupBy, filter, and zip are all inline functions that delegate to a corresponding *To variant (mapTo, flatMapTo, etc.) after creating a destination collection.
map and zip pre-allocate the destination ArrayList with the exact size when the source is a Collection, using collectionSizeOrDefault(10) to avoid internal array resizing.
flatMap and filter cannot pre-size the destination because the result length is unknown, so they start with a default-sized ArrayList.
groupBy deliberately uses a LinkedHashMap to preserve insertion order of keys as they first appear in the source Iterable.
The simple infix zip(other) is a one-liner that delegates to zip(other, transform) with a default lambda that creates a Pair.
filterNot and filterIndexed provide clearer alternatives to negated predicates and index-aware filtering, respectively.
The inline keyword copies the operator's loop and the lambda body into the call site, eliminating Function object allocations and enabling suspend/@Composable calls inside the lambda.
Conclusions

Kotlin's collection operators are not magic — they are thin, predictable wrappers around mutable-collection loops, which makes their performance characteristics easy to reason about.

The consistent *To delegation pattern is a design choice that rewards developers who learn it: you can call mapTo or filterTo directly with a reused ArrayList to cut allocations in hot paths.

Pre-allocation via collectionSizeOrDefault is a small but effective optimization that only works when the source is a Collection; Sequences and other arbitrary Iterables fall back to a default capacity of 10.

The LinkedHashMap choice in groupBy is a deliberate semantic guarantee, not an implementation detail — it ensures deterministic iteration order across runs and platforms.

Marking these operators as inline solves two problems at once: zero-cost lambda abstraction and context inheritance for coroutines and Compose, which would be impossible with regular higher-order functions.

Concepts & terms
collectionSizeOrDefault
An internal Kotlin helper that returns the size of the source if it is a Collection, or a default value (commonly 10) if it is an arbitrary Iterable. Used by map and zip to pre-allocate the destination ArrayList.
mapTo / flatMapTo / filterTo / groupByTo
The workhorse functions behind Kotlin's collection operators. Each takes a mutable destination collection and a lambda, iterates the source, and populates the destination. The public map, flatMap, filter, and groupBy functions are thin wrappers that create the destination and call these.
inline function
A Kotlin function whose bytecode (including any lambda arguments) is copied directly into the call site at compile time. This avoids creating Function objects for lambdas and allows the lambda to inherit the caller's context, such as suspend or @Composable capabilities.
LinkedHashMap
A Map implementation that maintains insertion order of keys. groupBy uses it internally so that the resulting map's keys appear in the order each key was first encountered in the source collection.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗