Learn when macros are the right structural tool in Clojure, when a function is better, and how to use macro expansion responsibly without hiding evaluation semantics.
Macro: A Clojure construct that transforms code before evaluation, letting you define new syntactic forms rather than just new runtime functions.
Macros are powerful, but they are also one of the easiest Clojure features to misuse. Many pages describe them as the way to “extend functionality,” which is true in a narrow sense but misleading in practice. Most behavior extension in Clojure should still happen with functions, higher-order composition, data, and namespaces. Macros are for the cases where evaluation structure itself needs to change.
Good macro use cases include:
If the problem can be solved by passing a function, using a wrapper, or composing data transformations, prefer that simpler route.
1(defmacro with-audit-context [event & body]
2 `(let [event# ~event
3 started-at# (System/currentTimeMillis)]
4 (try
5 (let [result# (do ~@body)]
6 {:event event#
7 :status :ok
8 :elapsed-ms (- (System/currentTimeMillis) started-at#)
9 :result result#})
10 (catch Exception ex#
11 {:event event#
12 :status :error
13 :elapsed-ms (- (System/currentTimeMillis) started-at#)
14 :message (.getMessage ex#)}))))
This macro does not just call a function. It shapes evaluation of the body itself. That is the kind of problem macros are meant for.
A common mistake is using a macro when a wrapper function would be clearer:
Those usually belong in higher-order functions or middleware. Once the body does not need special evaluation rules, a macro is often the wrong tool.
Macro users need to understand:
That is why macro quality depends on clarity, not just cleverness. Hygienic naming with auto-gensyms like result# helps avoid accidental capture, but it does not fix a confusing abstraction.
Macros are sometimes useful in structural-pattern pages because they can generate or standardize recurring scaffolding:
But they should not be treated as a default replacement for ordinary decomposition. A macro that hides simple logic behind magical syntax usually harms maintainability more than it helps.
This is by far the most common mistake. The result is code that is harder to debug and explain.
If readers cannot tell what will execute, how many times, or in what scope, the macro is too opaque.
DSLs can be elegant, but they are expensive to maintain when the domain is not stable enough yet.
Reach for a macro only when you need to control evaluation or define a genuine new form. Otherwise use functions, wrappers, middleware, or data-driven design. In Clojure, responsible macro use is not about proving you can extend the language. It is about doing so only when the language extension is genuinely the clearest solution.