Browse Learn Clojure Foundations as a Java Developer

Use macroexpand and macroexpand-1

Learn when to use macroexpand-1 for the next macro step and macroexpand for the fully expanded outer form, with review patterns for practical macro debugging.

macroexpand-1 and macroexpand are REPL tools for inspecting generated code. Use them before you debug a macro by trial and error.

The difference is scope:

Tool What it does Best use
macroexpand-1 Expands the outer macro call once. Reviewing the macro you just wrote.
macroexpand Repeatedly expands the outer form while it remains a macro call. Seeing the larger shape after nested outer expansion.

Neither tool evaluates the expanded code. They show code.

Start With One Step

1(defmacro log-value
2  [label expr]
3  `(let [value# ~expr]
4     (println ~label value#)
5     value#))

Use macroexpand-1 to verify the immediate transformation:

1(macroexpand-1
2  '(log-value "total:" (+ 1 2)))
3
4;; roughly:
5(clojure.core/let [value__auto__ (+ 1 2)]
6  (clojure.core/println "total:" value__auto__)
7  value__auto__)

This is enough to answer the important review questions:

Question Expansion evidence
Does (+ 1 2) run once? It appears once in the let binding.
Is the generated local safe? The local has an auto-generated name.
Does the macro return the original value? The generated form returns value__auto__.

Use Full Expansion Deliberately

macroexpand keeps expanding the outermost form while it is a macro. It does not recursively walk every nested expression inside the result.

1(macroexpand
2  '(when false
3     (log-value "total:" (+ 1 2))))
4
5;; roughly:
6(if false
7  (do
8    (log-value "total:" (+ 1 2))))

The outer when expands. The nested log-value remains because it is no longer the outermost form being expanded by macroexpand.

If you need to inspect the nested call, expand that nested form directly:

1(macroexpand-1
2  '(log-value "total:" (+ 1 2)))

Do Not Confuse Expansion With Evaluation

Expansion answers “what code will be generated?” Evaluation answers “what value does it produce?”

1(macroexpand-1 '(log-value "x" (+ 1 2)))
2;; returns a form
3
4(eval (macroexpand-1 '(log-value "x" (+ 1 2))))
5;; prints and returns 3, but eval is not normal macro debugging

In ordinary application work, avoid reaching for eval. Expand forms at the REPL, read them, then test the code that uses the macro.

A Debugging Workflow

Step Action
1 Write the desired call site first.
2 Run macroexpand-1 on a small representative call.
3 Check evaluation count, generated locals, and returned value.
4 Expand larger call shapes only after the small case is readable.
5 Add tests for behavior and, when useful, expansion shape.

Expansion review is especially useful during Java-to-Clojure migration because it turns “macro magic” into generated Clojure that looks like the code you already know how to review.

Knowledge Check

### What does `macroexpand-1` show? - [x] The next expansion of the outer macro call. - [ ] The final runtime value of the form. - [ ] Every nested function call result. - [ ] The JVM bytecode for the expression. > **Explanation:** `macroexpand-1` performs one macro expansion step. It is the best first tool for reviewing the macro you are writing. ### Why can `macroexpand` leave a nested macro call visible? - [x] It expands the outer form while that outer form is a macro call, not every nested form. - [ ] Nested macros are illegal in Clojure. - [ ] It evaluates the nested macro instead. - [ ] It only works on Java interop forms. > **Explanation:** `macroexpand` is not a tree walker for every subform. Expand the nested macro directly when that is the form you need to inspect. ### What should you check in the expansion of `log-value`? - [x] The caller expression appears once and the generated local is unique. - [ ] The macro compiles into a Java annotation. - [ ] The expression is converted to a string before running. - [ ] The macro prevents all exceptions. > **Explanation:** A useful expansion review checks duplicated evaluation, local capture, and whether the generated code returns the intended value.
Revised on Saturday, May 23, 2026