Learn what Clojure macros really are, how they fit into the reader-expansion-compile pipeline, and why they should be treated as language-shaping tools rather than fancy runtime functions.
Macro: A compile-time transformation that receives unevaluated Clojure forms and returns new Clojure forms for the compiler to process.
Macros matter in Clojure because they let you shape the language at the point where plain functions no longer help. A function receives values after evaluation. A macro receives the original code form before evaluation. That difference is what makes constructs such as when, doseq, ->, ->>, and cond-> possible in the way Clojure uses them.
The important consequence is that macros are not “better functions.” They live in a different phase of execution and solve different problems.
The diagram below shows the high-level path:
This is why macro questions usually turn into phase questions:
If you skip those distinctions, macros feel mysterious. Once you keep the phases separate, most of the mystery disappears.
A normal function cannot decide whether its arguments should be evaluated, because the arguments are already evaluated by the time the function runs. A macro can choose the shape of evaluation because it rewrites the code before runtime.
That is why macros are appropriate for:
And that is why they are usually the wrong tool for:
The following unless macro is pedagogical. In real Clojure code you would usually use the built-in when-not.
1(defmacro unless [test & body]
2 `(if (not ~test)
3 (do ~@body)))
Used like this:
1(unless (pos? balance)
2 (println "Balance is zero or negative"))
the macro expands into ordinary Clojure code. That means the lasting value is not the macro itself. The lasting value is understanding that the macro returned a new form for the compiler to continue processing.
Good macros are usually boring after expansion. They tend to emit:
ifletdoloop / recurThat is a good thing. The macro is there to make the source code expressive, not to produce inscrutable magic in the generated form.
Macros can absolutely improve clarity, but they also introduce risks:
That is why experienced Clojure codebases treat macros as a focused tool, not as a default abstraction mechanism.
Before creating a macro, ask:
If the answer to those questions is weak, you probably want a function instead.
The point of a macro is syntax and evaluation control, not routine runtime speed.
If users cannot predict the emitted code shape, the abstraction may be too magical.
Macros are easiest to understand when you inspect what they return.
Macros cannot help when the real decision depends on runtime data.
Use macros when you need to shape evaluation, introduce a small syntax layer, or build a declarative form that expands into plain Clojure. Keep the emitted code simple, inspect expansions often, and prefer functions whenever ordinary value-level abstraction is enough. In Clojure, macros are powerful precisely because they are rare and targeted.