When would you use `groupingBy().eachCount()` instead of `groupBy`, and what advantages does the `Grouping` abstraction give you?
answer
- groupingBy → lazy Grouping<T,K> object
- eachCount() = Map<K, Int>, no intermediate lists
- fold / reduce / aggregate per group in one pass
- aggregate's `first: Boolean` flags first-seen key
- Need the elements? still use groupBy
basics
~10 sUse groupingBy().eachCount() when you only need counts per key. It tallies directly into a map of counts instead of building lists first, so it's more efficient. Grouping also supports custom fold/reduce per group.
solid answer
~40 s`groupingBy(keySelector)` returns a lazy `Grouping<T, K>` object that doesn't materialize anything until you call a terminal operation. `eachCount()` produces `Map<K, Int>` by incrementing a counter per key — no intermediate `List<T>` is allocated, unlike `groupBy(...).mapValues { it.value.size }`. Beyond counting, `Grouping` exposes `fold`, `reduce`, `aggregate`, and `eachCountTo`/`foldTo`/etc. that let you accumulate a custom result per group in a single pass, with access to whether the key is seen for the first time. So `Grouping` is the right tool for streaming aggregations keyed by something derived — counting, summing, or building per-key accumulators — without the memory cost of grouping into lists first.
code
kotlin · 10 linesval words = listOf("apple", "avocado", "banana")
// count per first letter (no per-key lists allocated)
val counts = words.groupingBy { it.first() }.eachCount()
// {a=2, b=1}
// total length per first letter via fold
val totalLen = words.groupingBy { it.first() }
.fold(0) { acc, w -> acc + w.length }
// {a=12, b=6}go deeper
Knows eachCount() gives a frequency map of counts per key.
Explains why eachCount avoids the intermediate lists that groupBy().mapValues{size} would build.
Describes Grouping as a deferred abstraction and uses fold/reduce/aggregate for custom single-pass per-key accumulation.
Reasons about allocation/GC at scale, chooses *To variants to write into shared maps, and knows the trade-off vs retaining grouped members.
## The `Grouping` abstraction `groupingBy(keySelector: (T) -> K): Grouping<T, K>` returns a lightweight `Grouping` object. It is **deferred**: it just remembers the source and the key selector. Work happens only when you invoke a **terminal** operation on it. ## `eachCount()` — the common case ```kotlin val freq: Map<Char, Int> = "mississippi".toList().groupingBy { it }.eachCount() // {m=1, i=4, s=4, p=2} ``` This increments an `Int` per key. Compare with the naive alternative: ```kotlin word.groupBy { it }.mapValues { it.value.size } ``` The `groupBy` version first builds a full `List<Char>` for every key, then throws those lists away to count — wasteful in time and memory. `eachCount()` never allocates the lists. ## The full terminal toolkit `Grouping` supports a family of single-pass accumulators: - `eachCount()` / `eachCountTo(dest)` — counts per key. - `fold(initial) { acc, e -> ... }` and `fold(initialSelector, operation)` — seed then accumulate. - `reduce { key, acc, e -> ... }` — accumulate using the first element as the seed. - `aggregate { key, acc: R?, e, first: Boolean -> ... }` — the most general; `first` tells you whether this key is being seen for the first time, and `acc` is nullable until then. ```kotlin val totals: Map<String, Int> = orders.groupingBy { it.customer } .fold(0) { sum, order -> sum + order.amount } ``` ## Why prefer it - **No intermediate lists** for count/sum/reduce-style work — lower allocation and GC pressure. - **Single pass** over the source. - Composes with `*To` variants to write into an existing `MutableMap`. ## When `groupBy` is still right If you genuinely need the **grouped elements themselves** (e.g. to return `Map<K, List<T>>`), use `groupBy` — `Grouping` is for aggregating, not for retaining the members.
- What does the `first: Boolean` parameter of `aggregate` tell you?Whether the current key is being encountered for the first time, so you can initialize the accumulator (which is null on that first call) instead of updating it.
- Does groupingBy do any work before a terminal op?No. It only captures the source and key selector; iteration and accumulation happen when you call eachCount/fold/reduce/aggregate.
saying these in an interview costs you the question
- Saying eachCount() builds Map<K, List<T>> first then counts
- Believing groupingBy is eager / returns a Map directly
- Not knowing fold/reduce/aggregate exist on Grouping
- Recommending groupBy{...}.mapValues{it.value.size} as equally efficient