Using `partial`, `comp`, and `juxt`

Learn when Clojure composition helpers improve clarity, when thread macros are a better fit, and how `partial`, `comp`, and `juxt` solve different function-shaping problems.

Idiomatic Clojure is full of small functions wired into larger behavior. But not every helper that “composes functions” solves the same problem. partial, comp, and juxt all return functions, yet they shape those functions in very different ways.

partial: Pre-fills some arguments of a function and returns a new function that expects the rest.
comp: Builds a new function by feeding output from right to left through a chain of functions.
juxt: Applies multiple functions to the same input and returns all results in a vector.

The real question is not which helper is most clever. It is which one matches the data flow you actually need.

Use partial to Preconfigure a Function

partial is for fixing arguments now so callers can supply the remaining arguments later.

1(defn log! [level component message]
2  (println (str "[" level "] [" component "] " message)))
3
4(def warn-db! (partial log! :warn :db))
5
6(warn-db! "connection pool saturated")

This is useful when:

  • specializing a generic helper for one subsystem
  • passing callbacks into higher-order functions
  • capturing stable policy values without inventing a new wrapper every time

But partial stops helping when too many positional arguments are hidden. If the reader has to count slots mentally, a named helper is often clearer.

Use comp for Small, Reusable Pipelines

comp chains functions right to left into a new function.

1(def normalize-name
2  (comp clojure.string/capitalize
3        clojure.string/trim))
4
5(normalize-name "  alice  ")
6;; => "Alice"

comp works best when each stage is small and the data flow is linear. It is a strong fit for:

  • normalization helpers
  • validation or decoding pipelines
  • middleware-like function chains
  • small transformations passed into map, update, or other higher-order functions

Once the flow gets long, needs named intermediate values, or reads naturally from left to right over a concrete value, thread macros are often clearer:

1(-> user-input
2    clojure.string/trim
3    clojure.string/lower-case
4    keyword)

That style is often easier to scan than a long, deeply composed function.

Use juxt When One Input Needs Multiple Views

juxt says: apply these different functions to the same input and collect the results.

1(def summarize-order
2  (juxt :id
3        (comp count :line-items)
4        (comp #(reduce + %) (partial map :price) :line-items)))
5
6(summarize-order
7  {:id 101
8   :line-items [{:price 10} {:price 20} {:price 15}]})
9;; => [101 3 45]

juxt is especially useful for:

  • extracting several metrics from one value
  • building tuples of related derived values
  • preparing compound sort or grouping keys
  • computing multiple projections before packaging them into a map or vector

It is not a concurrency primitive. It is simply a concise way to fan one input out to several pure calculations.

These Helpers Solve Different Problems

Developers often blur these helpers together because they all return functions. But their jobs are distinct:

  • partial specializes a function by fixing arguments
  • comp chains outputs through a transformation pipeline
  • juxt fans one input out to several calculations

Seeing those roles clearly makes function-shaping decisions much easier.

Common Misuse Patterns

Overusing partial

If the partially applied function hides too much positional meaning, the code becomes harder to read than a named helper.

1;; Harder to read than it looks
2(map (partial update :status assoc :active true) records)

A small explicit helper is often better:

1(defn activate [record]
2  (assoc-in record [:status :active] true))

Overusing comp

If the transformation is long, branched, or tied to one concrete data flow, ->, ->>, or a plain function with let often communicates better.

Overusing juxt

If the returned vector has no clear positional meaning, use a map with named keys instead.

1{:min (apply min xs)
2 :max (apply max xs)
3 :avg (/ (reduce + xs) (count xs))}

That is often easier for later readers than remembering that position 2 is the average.

A Simple Choice Guide

    graph TD;
	    A["Need a new function"] --> B{"Pre-fill some arguments?"}
	    B -->|Yes| C["Use `partial`"]
	    B -->|No| D{"Chain outputs through steps?"}
	    D -->|Yes| E["Use `comp` or thread macros"]
	    D -->|No| F{"Apply several functions to same input?"}
	    F -->|Yes| G["Use `juxt`"]
	    F -->|No| H["Write a plain function"]

The diagram below is the main takeaway: these helpers are function-shaping tools, not interchangeable style markers.

Quiz

Loading quiz…
Revised on Thursday, April 23, 2026