Document Clojure code with useful line comments, reader-discard forms, REPL-friendly comment blocks, and docstrings that help Java teams understand functions without importing Javadoc habits blindly.
Clojure documentation is close to the code and close to the REPL. You still explain intent, tradeoffs, and public behavior, but the tools are different from Java’s //, /* ... */, and Javadoc.
The best Clojure comments help a reader understand why a value transformation exists, how a boundary behaves, or what a REPL experiment demonstrates. They should not narrate every small form.
| Tool | Example | Best use | Java comparison |
|---|---|---|---|
| Line comment | ;; normalize once at the boundary |
Explaining intent near code | // |
| Reader discard | #_(println "debug") |
Temporarily ignoring the next readable form | No exact equivalent |
comment form |
(comment ...) |
REPL scratch work kept in a namespace | Sometimes like a development notebook |
| Docstring | (defn f "..." [x] ...) |
Public function, macro, namespace, or var documentation | Javadoc intent, but runtime-accessible |
Use each tool for a distinct purpose. Do not turn source files into large prose essays, and do not leave misleading scratch code in production namespaces.
Clojure uses semicolons for line comments. In normal project code, ;; is the common choice for a comment on its own line:
1(defn taxable-total [order]
2 ;; Tax is calculated after discounts because coupons reduce taxable basis.
3 (+ (:subtotal order)
4 (:tax order)))
Prefer comments that explain business rules, boundary decisions, or non-obvious tradeoffs. Avoid comments that merely repeat the form:
1;; Poor: increments x.
2(inc x)
That comment adds no information.
#_The reader-discard form tells the Clojure reader to ignore the next form:
1(+ 1
2 #_(expensive-call)
3 2)
4;; => 3
This is useful for short-lived experimentation because it discards one complete form, not just one line. Remove reader-discarded production code once the experiment is over.
comment Blocks for REPL WorkMany Clojure teams keep a (comment ...) block at the bottom of a namespace for REPL examples:
1(ns billing.invoice
2 (:require [clojure.repl :refer [doc]]))
3
4(defn invoice-total
5 "Returns the invoice total in cents after discounts and tax."
6 [invoice]
7 (+ (:subtotal-cents invoice)
8 (:tax-cents invoice)
9 (- (:discount-cents invoice 0))))
10
11(comment
12 (invoice-total {:subtotal-cents 1000
13 :tax-cents 130
14 :discount-cents 100})
15
16 (doc invoice-total))
Important distinction: comment is a macro that ignores its body at evaluation time, but the body still must be readable Clojure forms. It is not a raw text block.
Docstrings are attached to Vars and are available in the REPL:
1(require '[clojure.string :as str])
2
3(defn normalize-email
4 "Returns a trimmed, lower-case email address. Does not validate deliverability."
5 [email]
6 (some-> email
7 str/trim
8 str/lower-case))
Good docstrings answer what a caller needs to know:
| Docstring should mention | Example question |
|---|---|
| Purpose | What does this function promise? |
| Input expectation | Can nil appear? What shape is the map? |
| Return value | Does it return a value, nil, a lazy seq, or a side effect result? |
| Boundary behavior | Does it validate, normalize, throw, retry, or log? |
| Important non-goals | What does it intentionally not do? |
Docstrings are especially valuable on public functions, protocol methods, macros, and namespace-level APIs. Private helper functions can often rely on clear names and local context unless behavior is subtle.
Use clojure.repl/doc during REPL work:
1(require '[clojure.repl :refer [doc]])
2
3(doc normalize-email)
This is one reason Clojure docstrings should be concise and caller-focused. They are not only generated website text; they are live development help.
| Java habit | Keep, adapt, or drop | Clojure guidance |
|---|---|---|
| Explain public API behavior | Keep | Use docstrings on public Vars |
| List every parameter mechanically | Adapt | Mention argument shape when it is not obvious |
| Generate large external docs first | Adapt | Keep REPL-visible docs useful; external docs can build on them |
| Comment every branch | Drop | Prefer small forms, good names, and tests |
| Use comments to compensate for unclear code | Drop | Improve the code shape first |
Weak version:
1(defn status-label [status]
2 ;; Check if status equals paid.
3 (if (= status :paid)
4 ;; Return Paid.
5 "Paid"
6 ;; Return Pending.
7 "Pending"))
Better version:
1(defn status-label
2 "Returns the UI label for invoice status. Unknown statuses are shown as pending until the workflow table is migrated."
3 [status]
4 (if (= status :paid)
5 "Paid"
6 "Pending"))
The second version documents a real product decision instead of narrating syntax.
#_ to discard one nested form, then remove it.(comment ...) block with one example call and one (doc ...) call.#_ for temporary form-level discard during experiments.(comment ...) blocks for readable REPL scratch forms, not arbitrary text.