Choosing `cond`, `case`, and `cond->` Well

Learn the real differences between `cond`, `case`, and `cond->`, and how to choose the right one for branching versus conditional transformation.

cond, case, and cond->: Three different tools that look related but solve different problems: sequential predicate testing, constant-value dispatch, and conditional transformation of one value.

These forms are often taught together because they all reduce nested if chains. But idiomatic Clojure depends on choosing the right one for the shape of the decision you are making. If you treat them as interchangeable, the code usually becomes either slower, less readable, or both.

Use cond for Ordered Predicate Checks

cond is the general-purpose tool for evaluating a series of conditions in order.

1(defn classify-temperature [c]
2  (cond
3    (< c 0) :freezing
4    (< c 10) :cold
5    (< c 20) :cool
6    (< c 30) :warm
7    :else :hot))

This reads well because each branch expresses a real predicate. The ordering matters. Once a truthy condition is found, cond returns that branch and stops.

Use cond when:

  • the branches depend on different predicates
  • the tests are not simple constant equality checks
  • branch order carries meaning

Use case for Constant Dispatch

case is more specialized. It dispatches on the value of one expression against constant choices.

 1(defn day-type [day]
 2  (case day
 3    :saturday :weekend
 4    :sunday   :weekend
 5    :monday   :workday
 6    :tuesday  :workday
 7    :wednesday :workday
 8    :thursday :workday
 9    :friday   :workday
10    :unknown))

This is the right choice when the question is:

“Given this exact value, which branch do I want?”

It is usually clearer and more direct than a long cond full of = checks.

Do Not Force case to Do Predicate Work

case is not a general-purpose branching tool. It is for constant matching, not arbitrary logic.

Bad use:

1(case amount
2  (> amount 100) :large
3  :small)

That is conceptually wrong because case does not evaluate those branch labels as predicates. When the branch depends on relational logic, type checks, or combinations of conditions, cond is the right tool.

cond-> Is Not a Branching Form

cond-> is frequently misunderstood because of its name. It does not pick one branch. It conditionally applies multiple transformations to the same starting value.

1(defn build-query [params]
2  (cond-> {:limit 50}
3    (:status params) (assoc :status (:status params))
4    (:sort-by params) (assoc :sort-by (:sort-by params))
5    (:admin? params) (assoc :include-deleted true)))

Each truthy test adds another transformation. That makes cond-> a good fit for:

  • option maps
  • request parameter shaping
  • query construction
  • conditional data enrichment

It is the wrong choice when you mean “pick one branch and stop.”

cond-> Does Not Short-Circuit

This is the nuance many people miss: cond-> evaluates every test-expression pair in order. If three tests are truthy, all three transformations happen.

That makes it different from:

  • cond, which stops at the first matching branch
  • some->, which stops on nil

Understanding this difference prevents subtle bugs in request or config builders.

    flowchart TD
	    A["What kind of decision is this?"] --> B{"One value matched to fixed constants?"}
	    B -- Yes --> C["Use `case`"]
	    B -- No --> D{"Ordered predicates?"}
	    D -- Yes --> E["Use `cond`"]
	    D -- No --> F{"Building or transforming one value conditionally?"}
	    F -- Yes --> G["Use `cond->` or `cond->>`"]

Prefer Clarity Over Clever Compression

It is possible to write all three forms in dense ways that save a few lines but cost readability. That usually happens when:

  • cond branches hide too much work inline
  • case contains many unrelated outcomes and should really be a lookup map
  • cond-> includes complicated expressions instead of small transformations

If one branch needs substantial logic, it is often better to call a named function from the branch rather than put everything inline.

Common Mistakes

  • using cond for long chains of exact constant equality where case is clearer
  • trying to use case with predicates or relational checks
  • assuming cond-> chooses only one branch
  • hiding too much work inside each branch instead of factoring it into named functions
  • treating all three forms as style variants of the same idea

Key Takeaways

  • cond is for ordered predicate checks.
  • case is for constant-value dispatch.
  • cond-> conditionally transforms one value and can apply multiple steps.
  • The right choice depends on the structure of the decision, not on personal taste.
  • Readability improves when each form is used for its natural job.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026