---
title: 'Structure and Headings'
source: 'https://academia.sh/en/courses/technical-writing/structure-and-headings'
course: 'Technical Writing and Documentation'
language: en
updated: '2026-08-17T18:10:50+00:00'
license: 'CC BY-SA 4.0'
---

# Structure and Headings

Heading layout measures the distance between the reader's question and its answer; across thirty-six visits, headingless text takes an average of 26.38 steps, type-headed 7.29, two-tier 7.00, and the two-tier layout pays for that gain by carrying 24 silently stale headings at the sixth version.

The previous lesson wrote the audience definition and measured the new reader resolving
all 56 claims with the full definition. Being able to resolve is not being able to
**reach**. The reader does not read a document start to finish; they arrive with a
question and search for the sentence that answers it.

This lesson's question is the cost of that search: **how many steps sit between the
reader's question and the claim that answers it, and how much does heading layout
shorten that distance?** A second question comes with it, tied to the course's own
axis: a heading is itself a sentence — does it go stale too?

## The Reader Does Not Read, They Scan

The reader of a technical text is not the reader of a literary text. They have a task in
hand, they are standing in the middle of that task, and they arrive at the text with a
question. Deciding to read the text from the start is rare; the norm is **scanning**.
The eye jumps from heading to heading, stops if one resembles the question, reads what
is under it, and moves on if it does not.

**Scannable text** is text built around this behavior. The measure of scannability is
not aesthetic; it is the **number of units** the reader has to read before reaching the
sentence they are looking for. This number can be measured, because both headings and
claims are countable units.

The measure's definition is this: reading a heading and saying "not my question" is
**one step**; reading a claim and saying "not my answer" is also **one step**. Both jobs
spend the reader's attention to the same degree. This equating may look crude, but
converting both into the same unit is the only way to see what headings actually buy.

## The Axis of Sectioning

Sectioning a document has more than one axis, and the axis chosen sets the cost of the
search. The course's first topic already gave one axis: **type**. A second axis comes
from the product itself: **topic**. A third option is nesting the two.

```text
# example heading layout, not executed

type-headed          topic-headed          type + topic
  Tutorial              Setup                 Tutorial
  How-To                Starting a              Setup
  Explanation             Measurement           Starting a
  Reference             Output Format             Measurement
                        Authorization           Output Format
                        Error                   ...
                        Settings              How-To
                                                Setup
                                                ...
```

The reader uses these two axes with different confidence. **Type** they infer from the
shape of the question: a reader asking "where do I start" looks for the tutorial, one
asking "what type is this field" looks for the reference. **Topic** they already know;
the reader is in the middle of their task and is aware which topic they are stuck on.
Since both axes are accessible to the reader, both are workable for sectioning.

Not sectioning is a choice too. Headingless text is a single flow, and the reader's only
option is scanning from the start. The measurement carries this option too, as a fourth
row, because it is the point of comparison.

The measurement's assumptions:

- **WD11** — The 56 claims come from the shared fixture: four types, four surfaces, 17
  claims embedded in a runnable example. The oracle is the fixture itself.
- **WD12** — The only thing this lesson adds is every claim's **topic**; the topic is
  chosen from six headings by the fixture's generator and does not look at the claim's
  type.
- **WD13** — A reader visit is a **question–topic** pair. All combinations of the six
  reader questions and the six topics are tried: **36 visits**. The questions come from
  the shared fixture, and two of them fall into no type.
- **WD14** — Reading a heading is one step, reading a claim is one step. The reader
  reads headings **in order** until they reach a matching one; they read the matching
  section's claims in order too.
- **WD15** — The reader correctly infers type from their question and topic from their
  own situation. The measurement is not a comprehension measurement, it is a
  **distance** measurement.
- **WD16** — If the answer is not found, the reader is counted as having scanned every
  heading and every claim; the cost of a not-found visit is the layout's total unit
  count.
- **WD17** — A heading is itself a claim and is bound to a surface: type names are
  bound to **concept**, topic names to **name**. A heading cannot be embedded in a
  runnable example; this is why **every heading that goes stale goes stale silently**.

## Measurement

```python
"""Structure and headings: in how many steps is the reader's question reached.

Part 1 - the spread of claims across topics and 36 reader visits.
Part 2 - step count in four heading layouts, and where not-found comes from.
Part 3 - a heading is a claim too: the true/stale/silent table of the heading set.
"""
SEED = 20260816
TYPES = {
    "tutorial":      {"flow": 9, "signature": 3, "name": 2, "concept": 1},
    "how-to":        {"flow": 6, "signature": 5, "name": 2, "concept": 1},
    "explanation":   {"flow": 1, "signature": 1, "name": 1, "concept": 9},
    "reference":     {"flow": 0, "signature": 13, "name": 2, "concept": 0},
}
FREQUENCY = {"signature": 2, "flow": 3, "name": 5, "concept": 12}
EMBEDDED = {"tutorial": 0.60, "how-to": 0.70, "explanation": 0.10, "reference": 0.00}
QUESTIONS = {"where do I start": "tutorial", "how do I do this": "how-to",
             "why is this like this": "explanation",
             "what type is this field": "reference",
             "what changed in which version": None,
             "why isn't this working": None}
TOPICS = ("setup", "starting a measurement", "output format", "authorization",
          "error", "settings")
VERSIONS = (0, 1, 2, 3, 6, 12)
# A heading is itself a claim and goes stale according to the surface it is bound to.
HEADING = {"headingless": {}, "type-headed": {"concept": 4},
           "topic-headed": {"name": 6}, "type + topic": {"concept": 4, "name": 24}}


def rng(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def claims():
    entries, no = [], 0
    for kind, mix in TYPES.items():
        embedded_target = 0
        for surface, count in mix.items():
            for _ in range(count):
                no += 1
                embedded_target += EMBEDDED[kind]
                embedded = embedded_target >= 1 and surface != "concept"
                if embedded:
                    embedded_target -= 1
                entries.append({"no": no, "type": kind, "surface": surface,
                                 "embedded": embedded})
    return entries


def assign_topic(entries, seed=SEED):
    """This lesson's one addition: every claim's topic."""
    r = rng(seed)
    for i in entries:
        i["topic"] = TOPICS[r(len(TOPICS))]
    return entries


def stale(surface, version):
    return version // FREQUENCY[surface] >= 1


A = assign_topic(claims())
VISIT = [(s, k) for s in QUESTIONS for k in TOPICS]
SECTION = {"headingless": ([None], [None]), "type-headed": (list(TYPES), [None]),
           "topic-headed": ([None], list(TOPICS)),
           "type + topic": (list(TYPES), list(TOPICS))}


def search(visit, layout):
    """Reading a heading and reading a claim both cost one step; a miss scans everything."""
    question, topic = visit
    kind, step = QUESTIONS[question], 0
    upper, lower = SECTION[layout]
    for u in upper:
        if u:
            step += 1
            if u != kind:
                continue
        for l in lower:
            if l:
                step += 1
                if l != topic:
                    continue
            for i in A:
                if (u and i["type"] != u) or (l and i["topic"] != l):
                    continue
                step += 1
                if i["type"] == kind and i["topic"] == topic:
                    return step, True
    return sum(HEADING[layout].values()) + len(A), False


print(f"claims {len(A)} | topics {len(TOPICS)} | reader visits {len(VISIT)}")
print("topic distribution: " + ", ".join(
    f"{k} {sum(i['topic'] == k for i in A)}" for k in TOPICS))
gap = [v for v in VISIT if QUESTIONS[v[0]] and not any(
    i["type"] == QUESTIONS[v[0]] and i["topic"] == v[1] for i in A)]
print(f"visit falling into a type {sum(1 for s, _ in VISIT if QUESTIONS[s])} | "
      f"falling into no type {sum(1 for s, _ in VISIT if not QUESTIONS[s])} | "
      f"type exists but no matching claim {len(gap)}")
print("coverage gaps:", [(QUESTIONS[s], k) for s, k in gap])
print()
print(f"{'heading layout':>14s} {'headings':>8s} {'average step':>13s} {'worst':>7s}"
      f" {'found':>7s} {'not found':>11s}")
for layout in HEADING:
    result = [search(v, layout) for v in VISIT]
    found = [a for a, b in result if b]
    print(f"{layout:>14s} {sum(HEADING[layout].values()):8d}"
          f" {sum(found) / len(found):13.2f} {max(found):7d}"
          f" {len(found):7d} {len(result) - len(found):11d}")
print()
print("heading claims - true/stale/silent")
print(f"{'heading layout':>14s}" + "".join(f" {'v' + str(s):>8s}" for s in VERSIONS))
for layout, mix in HEADING.items():
    total, line = sum(mix.values()), f"{layout:>14s}"
    for s in VERSIONS:
        stale_ = sum(n for y, n in mix.items() if stale(y, s))
        line += f" {f'{total - stale_}/{stale_}/{stale_}':>8s}"
    print(line)
```

```
claims 56 | topics 6 | reader visits 36
topic distribution: setup 13, starting a measurement 8, output format 11, authorization 10, error 7, settings 7
visit falling into a type 24 | falling into no type 12 | type exists but no matching claim 3
coverage gaps: [('tutorial', 'starting a measurement'), ('how-to', 'settings'), ('reference', 'output format')]

heading layout headings  average step   worst   found   not found
   headingless        0         26.38      56      21          15
   type-headed        4          7.29      19      21          15
  topic-headed        6          7.90      14      21          15
  type + topic       28          7.00      11      21          15

heading claims - true/stale/silent
heading layout       v0       v1       v2       v3       v6      v12
   headingless    0/0/0    0/0/0    0/0/0    0/0/0    0/0/0    0/0/0
   type-headed    4/0/0    4/0/0    4/0/0    4/0/0    4/0/0    0/4/4
  topic-headed    6/0/0    6/0/0    6/0/0    6/0/0    0/6/6    0/6/6
  type + topic   28/0/0   28/0/0   28/0/0   28/0/0  4/24/24  0/28/28
```

## What the Heading Shortens

The top table's biggest gap sits between the first and second rows. In headingless
text, the reader reaches the claim they are looking for in an average of **26.38**
steps; in the worst case, **56** units — the whole document. Adding four headings brings
the average down to **7.29**. Writing four lines cuts search cost to **a quarter**.

The second gap is much smaller, and it is the lesson's actual finding. Six topic
headings give **7.90** — **worse** than four type headings. The layout nesting both
axes gives **7.00**, only **0.29** steps better than the type-headed layout.
**Twenty-eight** headings add a quarter of a step on top of what four headings already
give.

Why so little? Because the scan inside a section is already short. Four types split 56
claims into groups of **15, 14, 12, and 15**; a second axis brings one of those groups
down to an average of two or three claims, but by the time the reader reaches that
group, they have already read six more sub-headings. The scanning gained is almost
exactly traded away by the heading-reading paid.

The worst-case column says something different: **56 / 19 / 14 / 11**. A deeper layout
barely moves the average but fixes the worst case. **Sectioning depth is a decision
made not for the average reader, but for the unluckiest one.**

These rows point to a second lever too. In the type-headed layout, heading-reading is at
most four steps; the rest of the average is scanning **inside** a section. So a
section's internal order matters as much as the heading count. Moving a frequently asked
claim to the top of a section does the same job without opening a new heading tier. This
is where the two types' traditional ordering comes from: a tutorial is ordered by
**sequence of steps**, because the reader carries out steps in order; a reference is
ordered **alphabetically**, because the reader knows the name they are looking for and
uses the order like an index. Both orderings answer the same question — how does the
reader decide the next unit?

## The Fifteen Visits That Are Never Found

**15** of the thirty-six visits find an answer in **none** of the four layouts, and this
number does not change from layout to layout. Headings cannot make findable what a text
does not contain. The fifteen failures come from two separate defects, and confusing
them is costly.

The first is **12** visits: the questions "what changed in which version" and "why
isn't this working" fall into no type. This is not a writing defect; it is the **limit
of the taxonomy** the shared fixture declares — the course's release-notes and
troubleshooting lessons build these two questions as separate types. For these visits,
what is needed is not adding a heading but **writing the missing type**.

The second is **3** visits: the question falls into a type, but not a single claim on
that topic was written in that type. The measurement names them one by one — starting a
measurement in tutorial, settings in how-to, output format in reference. This is a
**coverage gap**, and it is directly a writing list. Heading layout makes these gaps
visible; in headingless text, the same gap is a silent conclusion the reader reaches
only after scanning all 56 units.

The cost of a not-found visit rises as the layout gets deeper: **56** units are scanned
in headingless text, **84** units in the two-tier layout. A deep layout speeds up what
is found while making what is not found more expensive. If a document has many coverage
gaps, going deeper is the wrong investment.

## A Heading Is Also a Claim

The bottom table brings headings into the course's own axis. A heading is a sentence
and is bound to a surface. Type names are bound to **concept**: tutorial, how-to,
explanation, and reference are independent of the product's naming and stay true up to
the twelfth version — in the table, the four type headings run at **4/0/0** and only
turn **0/4/4** at version 12.

Topic names are bound to **name**, and a name changes once every five versions. Six
topic headings give **0/6/6** at the sixth version. The two-tier layout carries both:
**4/24/24** at version 6, **0/28/28** at version 12. When a product feature's name
changes, the twenty-four sub-headings carrying that name go wrong **at the same
instant**.

The third column is this table's real news: **every stale heading is silent.** A
heading cannot be embedded in a runnable example; no test reports that a heading names
something that no longer exists. The document builds, the page opens, the heading shows
up, and it is wrong.

The decision follows from reading these two tables together. The two-tier layout gains
**0.29** steps on average, and in return carries **24** silently stale headings at the
sixth version. Bringing the worst case from 19 down to 11 is a real gain, but its price
is a maintenance debt. The justification for going deeper cannot be the average; it can
only be the worst case, or the visibility of coverage gaps.

## Summary

- The measure of scannable text is not aesthetic; it is the number of units the reader
  reads before reaching their question. Reading a heading and reading a claim convert to
  the same unit.
- Headingless text takes an average of **26.38** steps, four type headings **7.29**,
  six topic headings **7.90**, the two-tier layout **7.00**. The first four headings cut
  cost to a quarter; twenty-four sub-headings add only **0.29** steps.
- The worst case falls as **56 / 19 / 14 / 11**: going deeper is a decision made not
  for the average reader, but for the unluckiest one.
- **15** of thirty-six visits find no answer in any layout; **12** are the taxonomy's
  limit, **3** are coverage gaps. Heading layout makes a gap visible, it does not close
  it; the cost of a not-found visit rises from **56** to **84** units in the deep
  layout.
- A heading is a claim: type names are bound to concept, topic names to name, and the
  two-tier layout carries **24** silently stale headings at the sixth version — since no
  heading can be embedded in an example, heading staleness is **always silent**.

## Next Step

Every measurement here rests on a single silent assumption: the reader can match the
word in their own question to the word in the heading. If a topic heading says
"authorization" and the reader calls the same thing "permission," the search breaks at
the very first step — the reader misses the match, enters the wrong section, or assumes
the text holds two separate things. The next lesson measures this matching: how much
does calling the same concept by two names lower the number of claims a reader can find,
and what exactly does each rule of a style guide measure.
