Writing User-Facing Docs
How to structure prose documentation with the Diátaxis framework, write a tutorial that works, and keep every code example correct with doctest.
Contents
- The Diátaxis Framework
- Choosing the Right Type
- Structuring a Tutorial
- Keeping Examples Correct with doctest
- README-to-Full-Docs Progression
The Diátaxis Framework
Diátaxis splits documentation into four types along two axes: practical vs. theoretical, and study (learning) vs. work (doing). Each type serves one user need; mixing them in a single page is the most common failure mode.
| Type | Serves | Oriented toward | User is asking |
|---|---|---|---|
| Tutorial | Learning | Practical / study | "Teach me by doing." |
| How-to guide | A goal | Practical / work | "How do I accomplish X?" |
| Reference | Lookup | Theoretical / work | "What are the exact parameters of X?" |
| Explanation | Understanding | Theoretical / study | "Why does it work this way?" |
- Tutorial — a guided lesson that takes a beginner from nothing to a small working result. You (the author) own the outcome; the learner just follows. No choices, no alternatives, no digressions.
- How-to guide — a recipe for a real task the user already knows they want. Assumes competence, states a goal, gives ordered steps. May offer alternatives ("if you use X instead...").
- Reference — dry, exhaustive, structured description of the machinery: signatures, parameters, return types, errors. For a Python library this is largely your autogenerated API docs (see the Sphinx configuration reference). Describe, never instruct.
- Explanation — discursive background: design decisions, trade-offs, how concepts fit together. Read away from the keyboard.
Choosing the Right Type
When a page feels wrong, it is usually serving two needs at once. Diagnostic questions:
- Am I teaching a skill, or helping accomplish a task? → tutorial vs. how-to.
- Am I telling the user what to do, or describing what exists? → guide vs. reference.
- Does this explain why? Move it out of the tutorial into an explanation and link to it.
A tutorial that stops to explain internals loses the beginner; a reference page padded with narrative becomes un-scannable. Keep each page to one job and cross-link between them.
Structuring a Tutorial
A tutorial succeeds when a first-time user can copy each step and see the promised result. Structure:
- Promise the outcome. Open with the concrete thing they will have built by the end.
- State prerequisites concisely. Install in one command:
uv add yourpackage - Move in small, verifiable steps. Each step is one action followed by the exact expected output, so the learner confirms success before continuing.
- Show complete, runnable code. No
...gaps, no "left as an exercise." Every snippet runs as written. - Point forward at the end. Link to the relevant how-to guides and reference — not more theory.
Rules that keep a tutorial trustworthy:
- Make it work first, every time — reliability matters more than realism.
- No choices or branches; a beginner cannot evaluate alternatives.
- Defer the why to an explanation page and link out.
- Actually run through it yourself on a clean environment before publishing.
Keeping Examples Correct with doctest
Doctest executes the >>> examples in your docstrings and prose and checks the printed output matches. This turns every example into a test, so docs cannot silently rot as the code changes.
A docstring example (the same style used across this skill):
def encode(latitude: float, longitude: float, *, precision: int = 12) -> str:
"""Encode geographic coordinates to a quadtree string.
Example:
>>> encode(37.7749, -122.4194)
'9q8yy9h7wr3z'
"""Run doctests on a single module directly:
uv run python -m doctest src/yourpackage/core.py -vBetter, wire them into your pytest suite so they run in CI alongside everything else. Collect docstring examples from modules, and standalone .md/.rst pages as text files:
uv run pytest --doctest-modules --doctest-glob='*.md'Make it the default by adding to pyproject.toml:
[tool.pytest.ini_options]
addopts = "--doctest-modules --doctest-glob='*.md'"Tips:
- Keep outputs deterministic. For dict/set ordering, floats, or object reprs, normalize the example or use
# doctest: +SKIPsparingly. - Prefer examples that show real, verifiable output over ones that print nothing — the check has value only when there is output to match.
README-to-Full-Docs Progression
Documentation grows with the project; do not build a full Sphinx site on day one.
- README only. Description,
uv add yourpackage, one quick-start snippet, and a link to whatever comes next. For a small library this is sufficient. - README + API reference. When the public surface grows past what a snippet can convey, add autogenerated reference docs from your docstrings (see the Sphinx configuration reference). Docstrings are now doing double duty as reference and as doctests.
- A tutorial. When new users need hand-holding to get their first result, add one guided tutorial. This is usually the highest-leverage prose page.
- How-to guides + explanations. As real questions recur ("how do I do X?", "why does it behave like Y?"), add focused how-to guides and explanation pages. Let actual user questions drive what you write, rather than filling out all four Diátaxis quadrants speculatively.
At every stage the README stays the entry point and links forward to the deeper docs.