Learn when `reduce-kv`, `into`, `group-by`, `frequencies`, and related helpers make collection code simpler, faster, and more idiomatic.
Collection helper: A function that captures a common transformation pattern so you do not have to rebuild it with lower-level loops or repeated manual reduction.
Clojure collection code stays idiomatic when you choose the helper that matches the shape of the job. reduce-kv, into, group-by, frequencies, and merge-with are powerful not because they are magical, but because they encode common patterns directly.
Many collection problems can be solved with a raw reduce, but that is not always the clearest answer. If the job is:
then Clojure usually already has a helper that says exactly that.
The question is not “can I solve this with reduce?” It is “what helper makes the intent most obvious?”
reduce-kv Is for Associative Collectionsreduce-kv shines when you are iterating over maps or other associative structures and both the key and value matter.
1(defn rename-keys [m]
2 (reduce-kv
3 (fn [acc k v]
4 (assoc acc (name k) v))
5 {}
6 m))
Compared with turning the map into a sequence of pairs first, reduce-kv is often clearer because the shape of the job is explicit: walk key-value entries and accumulate a new result.
Use reduce-kv when:
into Is the Default Builderinto is often the cleanest way to say “take this source and build that target.”
1(into [] (map inc) [1 2 3 4])
2;; => [2 3 4 5]
Or with sets:
1(into #{} (map keyword) ["dev" "ops" "security"])
2;; => #{:dev :ops :security}
The target collection communicates the intended output type, which is often more readable than a manually written reduction.
Some transformations show up so often that writing them from scratch just adds noise.
group-by1(group-by :status
2 [{:id 1 :status :open}
3 {:id 2 :status :closed}
4 {:id 3 :status :open}])
Use group-by when the result should be a map from derived key to items.
frequencies1(frequencies [:ok :ok :error :ok :warn])
2;; => {:ok 3, :error 1, :warn 1}
Use frequencies when the job is literally counting occurrences.
merge-with1(merge-with +
2 {:api 3 :db 1}
3 {:api 2 :queue 4})
4;; => {:api 5, :db 1, :queue 4}
Use merge-with when multiple maps share keys and you have a clear combination rule.
Collection helpers work best when each one carries a clear semantic step.
1(->> events
2 (group-by :service)
3 (reduce-kv
4 (fn [acc service xs]
5 (assoc acc service (count xs)))
6 {}))
This pipeline is readable because each step has a specific role:
That is usually better than one massive reducer that hides both steps inside one accumulator function.
Clojure helpers are often good performance choices too, especially when paired with transducers or a well-chosen target collection. But the first win is almost always clarity.
Only drop to lower-level reduction patterns when you have a real reason, such as:
Prematurely replacing expressive helpers with custom reducers often makes the code harder to trust.
Suppose you want a summary of order totals by region:
1(defn totals-by-region [orders]
2 (->> orders
3 (group-by :region)
4 (reduce-kv
5 (fn [acc region xs]
6 (assoc acc region
7 (transduce (map :total) + 0 xs)))
8 {})))
This is idiomatic because:
It reads like the business question itself.
reduce for a job group-by or frequencies already solves directlyreduce-kv when only values matter and plain sequence operations would be simplerinto communicates the target collection typereduce-kv is best when both map keys and values matter.into is the default builder when the target collection type should be explicit.group-by, frequencies, and merge-with usually say intent better than raw reduction.