Documentation with clojure.repl and Docstrings

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.

Start by Documenting Public API Boundaries

The highest-value documentation is usually on public namespaces and public vars.

Good candidates:

  • functions that other namespaces call directly
  • macros whose evaluation model may surprise readers
  • protocol methods that define an abstraction boundary
  • namespaces that represent a capability or subsystem

Lower-value candidates:

  • tiny private helpers whose names already make the behavior obvious
  • trivial wrappers whose only purpose is local composition

That does not mean private helpers should never be documented. It means the first priority is documenting the interfaces other readers will actually touch.

A Good Docstring Answers the Reader’s First Questions

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:

  • what the function returns
  • what input shape matters
  • what it deliberately excludes

That is usually more useful than repeating the function name in sentence form.

Namespaces Deserve Documentation Too

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:

  • explains the namespace purpose
  • hints at its architectural role
  • points readers to a more focused namespace for pure rules

For larger systems, that kind of orientation saves real time.

Use the REPL to Read, Search, and Inspect Documentation

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.

Write for Callers, Not for Authors

A common documentation mistake is writing from the perspective of the implementer:

  • “Uses a reducer and intermediate map”
  • “Calls helper functions and merges state”

That is sometimes useful, but it is usually secondary.

Callers care more about:

  • input contract
  • output shape
  • units and conventions
  • side effects
  • failure behavior

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.

Examples Help Most When the API Is Tricky

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:

  • macros
  • higher-order functions
  • configuration maps
  • data-oriented APIs with many optional keys

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.

Documentation Should Match the Real Design

Bad documentation often reveals design drift:

  • the docstring promises one shape but the function expects another
  • the namespace docstring describes an old role the file no longer has
  • comments explain workarounds that the code has already outgrown

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.

Generated Documentation Still Starts with Source Quality

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:

  1. write clear docstrings in source
  2. make namespace-level intent explicit
  3. use REPL tools to inspect what readers will actually see
  4. generate external docs only after the source metadata is worth publishing

That way the generated output reflects a real contract rather than stale filler.

A Healthy Documentation Loop

    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.

Common Mistakes

  • restating the function name without adding meaning
  • documenting implementation trivia instead of caller-facing behavior
  • omitting units, key names, or failure semantics
  • leaving namespace docstrings blank in larger systems
  • letting generated documentation stand in for source quality

Key Takeaways

  • Document public API boundaries first.
  • Write docstrings for callers, not just implementers.
  • Use clojure.repl tools to inspect documentation the way readers will.
  • Add examples when the API shape or evaluation model is easy to misuse.
  • If the documentation is hard to write clearly, the design may need simplification.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026