What are the *To variants (mapTo, filterTo, flatMapTo, mapNotNullTo, etc.) and when would you choose them over the plain operators?
answer
- *To writes into a destination you pass, returns it
- plain operators = *To with a default ArrayList/Set
- mapTo(mutableSetOf()) dedupes in one pass
- accumulate from many sources into one collection
- use for buffer reuse / fewer allocations; you own mutability
basics
~20 sThe *To versions do the same transform but write the results into a collection you supply, instead of creating a brand-new list. You use them when you want to control the destination type or gather results from several sources into one collection.
solid answer
~40 sEvery transform operator has a *To sibling — mapTo, filterTo, filterNotTo, mapNotNullTo, flatMapTo, filterIsInstanceTo, mapIndexedTo, etc. — that takes a `destination: C` (a MutableCollection) as the first argument, appends the produced elements to it, and returns that same destination (so calls chain). The plain operators are actually implemented in terms of these, defaulting the destination to a fresh ArrayList or LinkedHashSet. Reach for *To when you want to: (1) choose the destination type, e.g. dedupe via a MutableSet (`mapTo(mutableSetOf()) { ... }`); (2) accumulate results from multiple source collections into one destination across several calls; (3) reuse a preallocated buffer to cut allocations in a hot loop. Trade-off: you take on mutability and must manage the destination's lifecycle; the plain operators are cleaner for the common case.
code
kotlin · 6 linesval a = listOf("x", "yy", "zzz")
val b = listOf("ww", "v")
val lengths = HashSet<Int>()
a.mapTo(lengths) { it.length } // {1, 2, 3}
b.mapTo(lengths) { it.length } // adds 2,1 -> still {1, 2, 3}
println(lengths) // deduped across both sourcesgo deeper
Recognises *To writes results into a supplied collection rather than a new one.
Knows *To returns the destination and that it dedupes when given a Set, and pairs it with use cases.
Explains that plain operators are wrappers over *To, and uses them for destination control, accumulation, and allocation reduction.
Reasons about buffer reuse and capacity tuning in hot paths, mutability cost, and when the readability of plain operators outweighs *To's control.
## What they are For (almost) every transformation operator there is a `*To` overload that takes a **destination** collection as its first parameter, writes results into it (by `add`), and **returns that destination**. ```kotlin public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo( destination: C, transform: (T) -> R ): C { for (item in this) destination.add(transform(item)) return destination } ``` The plain `map` is literally `mapTo(ArrayList(collectionSizeOrDefault(10)), transform)`. So `*To` is the *primitive*, and the convenient operators are thin wrappers that pick a default destination. Members of the family: `mapTo`, `mapIndexedTo`, `mapNotNullTo`, `mapIndexedNotNullTo`, `filterTo`, `filterNotTo`, `filterNotNullTo`, `filterIsInstanceTo`, `flatMapTo`, `flattenTo`, `associateTo`, etc. ## Why use them ### 1. Control the destination type ```kotlin val uniqueLengths: MutableSet<Int> = words.mapTo(mutableSetOf()) { it.length } // dedupes as it goes ``` With plain `map` you'd get a `List` and then call `.toSet()` — an extra collection. `mapTo(mutableSetOf())` dedupes in a single pass. ### 2. Accumulate from multiple sources into one collection ```kotlin val all = ArrayList<String>() listA.mapTo(all) { it.name } listB.filterTo(all) { it.isActive }.map { /* ... */ } // returns 'all' ``` Because each `*To` returns the **same** destination, you can merge several transforms' output without intermediate lists. ### 3. Reuse a buffer / reduce allocations In a hot path you can preallocate one `ArrayList(expectedSize)` (or clear and reuse) and write into it, avoiding repeated allocations from chained plain operators. ## Trade-offs - You opt into **mutability** and must manage the destination's lifecycle and capacity. - The plain operators are clearer and idiomatic for the **common one-shot case**; reach for `*To` only when destination control or accumulation actually pays off. - `*To` returns the destination, enabling fluent chaining: `result = xs.filterTo(dest) { ... }`. ## Related idea `associateTo`, `groupByTo`, `toCollection(dest)` follow the same pattern, so the convention generalises across the stdlib.
- What does mapTo return, and why does that matter?It returns the destination collection you passed in, which lets you chain or accumulate fluently and gather results from several operations into one collection without intermediate lists.
- How are the plain operators related to the *To variants?The plain operators are implemented on top of the *To variants: they call the *To version with a default fresh destination (typically an ArrayList sized from the source, or a LinkedHashSet for set-producing ones).
saying these in an interview costs you the question
- Thinking *To creates a new list and ignores the destination argument
- Not knowing *To returns the destination
- Believing plain operators and *To are unrelated implementations
- Using map().toSet() when mapTo(mutableSetOf()) would dedupe in one pass
- Forgetting *To appends (does not clear) the destination