Learn how to write hygienic macros in Clojure by avoiding variable capture, controlling evaluation count, using generated symbols correctly, and keeping expansions narrow and predictable.
Macro hygiene: Writing a macro so its expansion does not accidentally capture caller variables, shadow names unexpectedly, or evaluate forms more times than the caller expects.
Clojure macros are not hygienic by default in the strict automatic sense some languages provide. That means the macro author is responsible for protecting the caller from expansion mistakes.
The two classic hygiene failures are:
If you prevent those consistently, most macro code becomes much safer to use.
A macro can accidentally introduce a binding that collides with one from the caller:
1(defmacro bad-report [expr]
2 `(let [result ~expr]
3 {:result result
4 :ok? (boolean result)}))
If the caller already uses a local name such as result, the expansion can become confusing or outright wrong. The fix is to use generated symbols for macro-introduced locals.
1(defmacro good-report [expr]
2 `(let [result# ~expr]
3 {:result result#
4 :ok? (boolean result#)}))
The result# syntax creates a unique symbol under syntax-quote, which is one of the safest everyday hygiene tools in Clojure.
Another common problem is evaluating the caller’s form multiple times:
1(defmacro bad-when-let [expr & body]
2 `(if ~expr
3 (do ~@body ~expr)))
If expr performs I/O, mutation, or expensive computation, the semantics are now wrong or wasteful. Hygienic macro design usually binds the incoming form once and reuses the generated symbol.
One of the best macro best practices is to keep most of the logic outside the macro itself:
This keeps expansions easier to test and reduces the temptation to embed large amounts of compile-time cleverness.
Manual list construction can work, but syntax-quote is safer for everyday macro writing because it:
That does not mean list processing is never useful. It means the default happy path should favor readable emitted code.
Macro APIs often have hidden expectations:
Good docs matter more for macros because the expansion behavior is less obvious than a function call.
That invites accidental capture or shadowing.
This is both a correctness bug and a debugging nightmare.
Macros should shape syntax, not become a second runtime.
If the emitted form is too magical, the macro is probably too ambitious.
Use generated symbols for macro-introduced locals, bind caller expressions once unless repetition is deliberate, keep most substantive logic in helper functions, and make the expansion as small and legible as possible. In Clojure, good macro hygiene is mostly disciplined simplicity plus respect for the caller’s evaluation model.