How to write docstrings that actually help readers and use clojure.repl to explore public APIs from the REPL.
Docstring: Inline documentation attached to a namespace, function, macro, var, or protocol member through metadata so it can be read directly from tooling and the REPL.
Documentation in Clojure works best when it stays close to the code. A good docstring is not marketing text and not a duplicate of the implementation. It tells readers what a public entry point is for, what shape of input it expects, what it returns, and which caveats matter enough to know before using it.
That matters because Clojure developers often work through the REPL, editor integration, and source browsing rather than jumping first to a separate website. If the docstrings are weak, the interactive workflow gets weaker too.
The highest-value documentation is usually on public namespaces and public vars.
Good candidates:
Lower-value candidates:
That does not mean private helpers should never be documented. It means the first priority is documenting the interfaces other readers will actually touch.
Compare these two styles.
Weak:
1(defn calculate-total
2 "Calculates total."
3 [order]
4 ...)
Stronger:
1(defn calculate-total
2 "Returns the final payable total in cents for an order map.
3
4 Expects `:line-items` and optional `:discount-cents`.
5 Does not apply tax or shipping."
6 [order]
7 ...)
The stronger version helps because it answers:
That is usually more useful than repeating the function name in sentence form.
Namespace docstrings are easy to skip, but they are often the fastest way to orient a reader.
1(ns my-app.billing.invoice-service
2 "Application-facing invoice workflows.
3
4 Coordinates billing rules, persistence, and outbound notifications.
5 Keep pure pricing rules in `my-app.billing.rules`."
6 (:require [my-app.billing.rules :as rules]
7 [my-app.billing.store :as store]))
That short description does several things:
For larger systems, that kind of orientation saves real time.
The clojure.repl namespace gives you a compact documentation workflow without leaving the running system.
Useful tools include:
1(require '[clojure.repl :as repl])
2
3(repl/doc map)
4(repl/find-doc "reduce")
5(repl/source merge-with)
6(apropos "partition")
Each one answers a slightly different question:
doc asks “What is this for?”find-doc asks “What documented symbol sounds relevant?”source asks “How is this implemented?”apropos asks “What names even exist around this idea?”This is one reason concise, honest docstrings matter so much in Clojure. They improve the interactive discovery loop.
A common documentation mistake is writing from the perspective of the implementer:
That is sometimes useful, but it is usually secondary.
Callers care more about:
For example:
1(defn fetch-user!
2 "Fetches a user record from the identity service by user id.
3
4 Returns a normalized user map on success.
5 Throws `ex-info` with `:type :identity/unavailable` on transport failure."
6 [client user-id]
7 ...)
Now the caller knows how to use the function safely before reading the body.
Not every docstring needs an example. But when shape, evaluation, or usage is easy to misunderstand, a small example pays off.
That is especially true for:
Example:
1(defn group-by-status
2 "Groups order maps by `:status`.
3
4 Example:
5 (group-by-status [{:id 1 :status :new}
6 {:id 2 :status :paid}])
7 ;; => {:new [{:id 1 :status :new}]
8 ;; :paid [{:id 2 :status :paid}]}"
9 [orders]
10 ...)
The example should reduce ambiguity, not inflate the docstring.
Bad documentation often reveals design drift:
That is why documentation is not a final polish step. It is a design check.
If the behavior is too messy to explain briefly, the API may need refactoring more than it needs more prose.
Tools that build reference docs from source are useful, but they do not rescue weak content. If the source metadata is vague, the generated site will simply be a nicely formatted vague site.
The right order is:
That way the generated output reflects a real contract rather than stale filler.
flowchart TD
A["Design public API"] --> B["Write docstring at the boundary"]
B --> C["Inspect with doc / find-doc / source"]
C --> D{"Would a new reader understand usage?"}
D -->|No| E["Clarify contract, examples, or naming"]
E --> C
D -->|Yes| F["Publish and maintain with the code"]
This keeps documentation tied to the development loop instead of treating it as separate clerical work.
clojure.repl tools to inspect documentation the way readers will.