Macro Hygiene and Best Practices in Clojure

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:

  • variable capture
  • repeated evaluation

If you prevent those consistently, most macro code becomes much safer to use.

Variable Capture Is the First Real Danger

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.

Evaluate Inputs Exactly Once Unless Repetition Is Intentional

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.

Use Functions for Real Logic, Macros for Syntax

One of the best macro best practices is to keep most of the logic outside the macro itself:

  • validate shape if needed
  • compute helper values in ordinary functions
  • let the macro focus on emitting forms

This keeps expansions easier to test and reduces the temptation to embed large amounts of compile-time cleverness.

Prefer Syntax-Quote over Manual List Building

Manual list construction can work, but syntax-quote is safer for everyday macro writing because it:

  • qualifies symbols predictably
  • works naturally with generated symbols
  • makes the emitted shape easier to read

That does not mean list processing is never useful. It means the default happy path should favor readable emitted code.

Document the Caller Contract

Macro APIs often have hidden expectations:

  • which forms may be repeated or delayed
  • which names are introduced
  • whether side effects in arguments are safe
  • whether the macro expects binding forms, symbols, or arbitrary expressions

Good docs matter more for macros because the expansion behavior is less obvious than a function call.

Common Failure Modes

Introducing Local Names Without Gensyms

That invites accidental capture or shadowing.

Evaluating Caller Forms More Than Once

This is both a correctness bug and a debugging nightmare.

Putting Complex Business Logic Inside the Macro

Macros should shape syntax, not become a second runtime.

Writing Expansions That Need a Decoder Ring

If the emitted form is too magical, the macro is probably too ambitious.

Practical Heuristics

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.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026