Last modified: 2026-11-05 16:37:15 (PST)
AI-Assisted Authorship
This chapter was written with the assistance of GitHub Copilot, which expanded on outlined ideas and draft notes provided by the author. The content represents the author’s perspective and has been reviewed for accuracy, but the detailed prose was generated through AI assistance.
Lecture notes, slides, and problem sets teach rather than report. The general rules for scientific writing in the other chapters still apply. This chapter adds the rules that apply to teaching materials in particular.
A teaching document and a paper have different readers, so they follow different rules. The reader of a teaching document is learning the material. The reader of a paper is a researcher who already knows the field and wants to judge new work.
| Teaching materials | Writing for peers (a paper) | |
|---|---|---|
| Reader | A learner who needs each term defined before it is used. | A researcher in the field who knows the standard terms. |
| Purpose | The reader can do the method afterwards. | The reader can judge and build on the new result. |
| Standard material | Stated in full, with its definitions, notation, and worked examples. | Cited, not restated. |
| Order | An exercise first, then the definition, the result, and its proof. | The standard paper structure. |
| Examples | Every definition is followed by an example, and every example by an exercise. | Examples appear where they support a claim. |
| Repetition | One written version of each result is reused wherever it is needed. | Each result is stated once, where the argument needs it. |
Four consequences follow for how you write.
The rest of this chapter gives the rules for teaching materials. The chapters on paper organization and manuscript submission give the rules for papers.
Active learning, in which students work through tasks themselves during class, improves exam performance over lecture alone (Freeman et al. 2014). The exercise-first structure is this guide’s own convention for putting that result into practice. Pose each task as an exercise, give students time to try it, and then show the solution.
Pose every data manipulation or analysis as an exercise, and show the code in the solution. Do not show the code first and explain it afterwards.
Pose every calculation, derivation, or proof as an exercise, and put the worked steps in the solution.
Ask the question that a definition or theorem answers before stating it. The solution answers informally, in plain words, and the formal #def- or #thm- div follows the solution div.
Restate every result that a solution first derives in its own theorem-type div (#thm-, #lem-, #cor- or #prp-) after the exercise (Ezra, 2026-10-29). The solution shows the working, and the theorem div states the result by itself, so a reader can find and cite it. The check-restated-results workflow in gha warns about a derivation solution that a pull request edits and that no theorem-type div follows (gha#1309). The check never sweeps existing content: restate the result when you edit the passage anyway. <!-- restated-ok: reason --> in the solution exempts a result that is stated elsewhere. A restated theorem passes review only when it meets these four conditions:
Give each bottom-level prompt its own #exr- div and its own #sol- div, in order, rather than one exercise with parts (a) through (e). Any exercise with several parts is split, however the parts are marked: letters, numbers or bullets. Put the shared setup in the first exercise, and begin each later exercise by citing it, as in “Make the assumptions of the first exercise.” Later exercises can use earlier results through cross-references.
Split the solution the same way as its exercise (Ezra, 2026-10-28). Each #sol- of a split exercise answers only its own #exr-, takes its slug from that exercise (see the slug rule below), and sits in its own subfile after its exercise. This covers code: a lab solution puts each step’s code, output and figure in the solution of the exercise that asks for it, not all in the first solution. A solution does not label parts such as “(b)” or “Part 2”, and does not draw a figure that a later exercise asks for. Code that a later part needs stays in the earliest solution that needs it, because the page shares one session per language. The check-split-solutions workflow in gha enforces this (gha#1290).
Make each exercise ask for one thing (Ezra, 2026-10-27). A prompt that says “compute the slope, and compare it with the fitted value” asks for two things, so split it into two exercises, each with its own solution. Split a list item or step inside an exercise the same way, into separate exercises. Phrases such as “and then”, “and also”, and “and compare” mark a second ask.
Put the instruction or question of an exercise in its own paragraph, apart from the scenario information (Ezra, 2026-11-02). The scenario paragraphs give the data, the setting and the definitions that the exercise needs, and the last paragraph asks the question or gives the instruction (“Show that …”, “Which model …?”). A reader scanning for the task then finds it without rereading the setup. A paragraph that runs from the scenario into the task hides the task. The check-exercise-question-paragraph check fails a pull request that adds such a paragraph. Put <!-- question-paragraph-ok: reason --> inside an exercise that it misreads.
On slides, put a slide break between an exercise and its solution, so the prompt is on screen alone while students work.
Give each solution the slug of its exercise, with sol- in place of exr-.
Put a hint in a callout titled “Hint” inside the exercise div, not in italic text.
Give every exercise a hint that links to the sections of the notes it draws on (Ezra, 2026-11-04), in a lab as in a homework. Where an exercise asks for code, link the section of the programming primer that introduces each command it needs, and link each definition or result the exercise rests on. Use a direct anchor link on the longest phrase that names the thing, as Link the words, not a trailing citation describes. The hint points the reader to the material and does not give the answer.
Where the solution has a tab for each language, a hint that names a command of one language names the other language’s counterpart too (Ezra, 2026-11-04). Say which language each command belongs to, as in “.predict in Python, predict() in R”, and link the primer example whose tabs show both. A reader working in the other language otherwise finds only a name that does not exist in their language.
Give each exercise the background that its title implies, and say why the reader should care (Ezra, 2026-10-29). Whatever the title tells the reader about the object, the body also says: name the object, link its definition, and say where it arises and what it is used for. For a title such as “Maximizing a gamma-family kernel”, the body says what a gamma distribution and its kernel are, and that maximizing the kernel finds the mode without computing the normalizing constant. A body that only restates the formula leaves a reader who skipped the title with a formula and no reason to care. The first exercise of a split series carries the background, and each later exercise begins by citing it, as the splitting rule above says.
Example 1 (Asking before defining)
❌ Generalization is the ability of a model to perform well on new data.
✅
The revised version lets students attempt the idea before they see the formal definition.
Make every unit of analysis as concise and minimal as possible. The units are mathematical expressions and divs of every kind: definitions, theorems, lemmas, corollaries, examples, remarks and proofs. When a unit grows complicated, do not write the complexity into it. Abstract the complexity out into smaller, atomic definitions, results and expressions, each with its own name and identifier, and glue the pieces together with hyperlinks.
This is the compositional approach of modular programming: a program is built from small, single-purpose modules that call one another, and a maintainer changes one module without reading the rest. The notes work the same way. A theorem links to the definitions it uses, a proof links to the lemmas it uses, and an expression is written with the named pieces it is built from.
When a unit still looks long, ask what could be named and moved out.
Example 2 (Splitting one long definition)
❌ One div defines the ridge estimator, defines its penalty, and proves that it shrinks the coefficients.
✅ Three divs: a definition of the penalty, a definition of the ridge estimator that links to the penalty, and a theorem on shrinkage that links to both.
The ✅ version lets a later chapter link to the penalty alone, and each piece fits on a slide.
Follow a “Yes” or “No” with what the answer means in the words of the question, as in “No: it’s not possible that the advanced network has a test error of 0.25,” not a bare “No.” (Ezra, 2026-10-28). A reader who sees only the first line then still knows what the answer claims, and a bare “No.” leaves open what is being denied. The rule covers solutions, worked examples and any other answer to a question.
Example 3 (Saying what a “No” means)
❌ No. The irreducible error is a lower bound on the expected squared prediction error.
✅ No: it’s not possible that the advanced network has a test error of 0.25. The irreducible error is a lower bound on the expected squared prediction error.
The ✅ version states the conclusion in the question’s own words, so the reasons that follow support a claim the reader has already seen.
When a prompt asks several questions, answer each one in its own paragraph, or in several paragraphs if it needs them (Ezra, 2026-10-28). A solution to “Which model underfits, which overfits most, and which would you choose?” has one paragraph for the underfitting model, one for the overfitting model and one for the choice. A reader looking for one answer then finds it without reading the others. No automated check enforces this rule, because a script cannot tell how many questions a prompt asks.
Defining terms clearly covers what a good definition contains. For teaching materials, four further rules keep definitions findable.
#def- div. A bolded keyword usually marks an inline definition.#def- div. A term defined inside another term’s div, or two terms joined in one title (“Regression and classification”), has no identifier of its own, so nothing can link to it.#def-, #thm-, #lem-, #cor-, #prp-, #cnj-, #exm-, #rem-, #exr-, and #sol-. A special case, such as a linear evaluation function, gets its own div after the general case, and the two link to each other.#def- div. Put the properties of the defined object, such as the symmetry and idempotence of a matrix, in a separate #thm-, #lem- or #prp- div after it, with a proof where one is known. Each abstract div then gets its own example, as the rule below requires.Every alternative name for the same concept stays in the div of the term it names and is treated like the primary name: it appears in the div title and is bold where it first appears in the definition. Name synonyms and near-synonyms where the term is defined, and say which one the notes use. A student who encounters one name here and another in a textbook cannot tell whether the difference matters. For a near-synonym, name the seam: critical points include the stationary points and also the points where the derivative does not exist.
When a symbol or convention has more than one common form, state the one the notes use and name the others a reader will encounter elsewhere. Put the alternatives in the definition div or in a callout beside it. For example, some sources include 0 in the natural numbers and others exclude it, so write \(\mathbb{N}_0\) or \(\mathbb{N}^+\) to make the choice explicit.
Define a quantity in the form that needs the fewest symbols, and state the textbook form as a theorem that follows. For example, define variance as the mean squared deviation \(\operatorname{E}\left[\operatorname{devn}(X)^2\right]\), where \(\operatorname{devn}(X) = X - \operatorname{E}[X]\), and then prove that it equals \(\operatorname{E}[X^2] - (\operatorname{E}[X])^2\).
Parsimony outranks convention when choosing a primary definition. The conventional form still appears in the notes, as a named result.
When several courses or repositories have already written the same definition, theorem, or exercise, compare the versions on notation, decomposition, parsimony, and clarity. Use the best one, or merge them, and make it the single copy. Other places include or link to that copy instead of keeping a second wording that can drift.
Students can ask an AI assistant for a complete solution to any homework problem, so a problem is no more secure for never having been published (Ezra, 2026-11-04). Choose assignments for their fit with the course, not for novelty.
Check the reference text itself before naming its datasets. Open the chapter and list the dataset each section uses, rather than recalling them.
Example 4 (Showing the data at first mention)
❌ Slides 3 to 9 discuss the spam data’s 57 predictors, its class imbalance and the loss we will minimize. Slide 10 shows a table of the first rows.
✅ Slide 3 introduces the spam data and shows a table of its first rows and a plot of one predictor by class. Slides 4 to 10 discuss the predictors, the class imbalance and the loss, and refer back to that table and plot.
The ✅ version lets every later slide refer to something the reader has already seen.
This rule applies to all teaching materials, in every course and every teaching repository.
resources: when the site publishes it.#exm- div. The rule covers each #def-, #thm-, #lem-, #cor- and similar div. The example of a related result does not exempt a definition: a theorem that uses a definition gets its own example, and so does the definition. The ai-config rule links here.#exm- div a figure of its data, model or results (Ezra, 2026-10-23). When an example has no figure, state the reason in that div. Put the figure inside the div, or include a subfile whose name ends in _fig inside the div. When an example has nothing to plot, such as a single number, add <!-- no-figure: reason --> inside the div, replacing reason with why there is nothing to plot. The check-exm-figures workflow in gha reports, in a pull request, each example that has no figure and no no-figure comment (gha#1193).check-visualization-figures workflow in gha reports, in a pull request, each such div that has no figure in it or in its solution, and a <!-- no-figure: reason --> comment inside the div suppresses that report, as it does for an example (gha#1262).@fig-id, and say what the reader should see in it. A figure that no sentence cites or interprets is decoration.#exm-, #exr- or #sol- div (Ezra, 2026-11-02). A figure outside the div leaves the example or exercise incomplete when the reader meets it alone, and moves with the wrong text when the page is reorganized. Leave a figure that several examples share outside every div, and cite it from each. The check-figure-placement workflow in gha reports, in a pull request, each figure on a changed line that no prose cites and each figure placed directly after an example, exercise or solution div without being inside it. A <!-- figure-placement: allow --> comment on the figure or inside its div suppresses the report for a shared figure (gha#1378).Reading a worked example is passive. A reader who has just seen one method applied learns more by applying it to a new case than by reading a second example. So each example gets a practice exercise, with its solution, right after it.
#exm- div, add an #exr- div that asks the reader to do the same thing for a different case, followed by its #sol- div. The exercise changes the data, the function or the question, so the solution is not a copy of the example.Example 5 (An example followed by a practice exercise)
✅
The example shows the method once. The exercise asks the reader to repeat it on a new variable before moving on, and the solution’s numbers still come from running code (see Use real data and compute every number).
These rules apply to all teaching materials, in every course and every teaching repository, not only to one course.
group="language". Start a new tabset whenever prose or a new step sits between code chunks, so the reader sees one step’s code in one language at a time. The shared group makes one choice of language apply to every tabset on the page, as long as the tab headings use identical text in every tabset, such as always “R” and “Python”.statsmodels’ sm.OLS, as ISLP_labs does. If an exercise departs from the book, say why in the lab.github.com link into a private repository, or to “the copy in this repository”. Link a data file from the published site, from a public repository or package, or from the data’s original public source.An instruction that applies to only some readers reads as an order to all of them. Put the condition first, then the instruction, as in “If you use a language other than R or Python, …”.
The labs in the reference text write optional work this way. They state the condition first and then the consequence: “If you have not installed ggplot2, then the code below will still run, but the plot will be less attractive” (James et al. 2021, sec. 10.9, p. 445). They give an alternative to the reader who is short of time: “If you are in a rush, then you could set B equal to a smaller value (e.g. B = 500)” (James et al. 2021, sec. 13.6, p. 587). They label a step that has no condition and say when to do it: “this step is optional and should be performed only if we want each gene to be on the same scale” (James et al. 2021, sec. 12.5, p. 543).
❌ In another language, use a port of
SyncRNGto get the same data; without one, draw your own data from the same model.✅ If you use a language other than R or Python, use a port of
SyncRNGto get the same data. If no port exists, draw your own data from the same model; your numbers will differ slightly from the key’s.
This rule applies to every executed code chunk in teaching materials, in every course and every teaching repository, in every language.
#| include: false. When the chunk that computes those values is already hidden, merge the storing code into that chunk.None and shows the chunk as one code block per printed value, such as a <matplotlib.lines.Line2D object at 0x...> line after every ax.plot(...). End each such call with a semicolon, or assign its value.This rule applies to all R code in teaching materials, in every course and every teaching repository.
lm() or glm(), show its results with modern packages, not with base R output.parameters() |> print_md()) or gtsummary (tbl_regression()), not with summary() or coef(summary()) printouts.plot(), lines() and abline().check_model() for diagnostic plots.model_performance() or broom’s glance().This rule applies to all R code in teaching materials, in every course and every teaching repository.
select(), not with df[, c("a", "b")] or df[c("a", "b")].filter(), not with df[df$x > 0, ] or subset().mutate(), not with df$new <- ....drop_na(), not with df[complete.cases(df), ].bind_rows(), not with rbind().tibble(), not with data.frame().|>.tibble(x, y) |> pander(), not pander(tibble(x, y)), and sizes |> lapply(f) |> bind_rows(), not bind_rows(lapply(sizes, f)). Give a call that would sit inside an argument, such as data = filter(df, train) in a model call, a name on its own line first.This rule applies to code that appears in more than one place in teaching materials.
filters: [include-code-files] to the front matter, then write a code block with an include attribute, such as ```{.python include="script.py"}.start-line and end-line, to show part of a file. Prefer a range that names the lines you want over a copy of those lines.quarto add quarto-ext/include-code-files, and commit the _extensions directory it creates.This rule applies to all reader-facing code in teaching materials, in every course and every teaching repository, in every language.
# add 1 to x above x <- x + 1, gives the what without the why. Say what the step accomplishes and why the analysis needs it, for example # center age so the intercept is the mean outcome at the average age.code-fold by what the code does and whether the prose mentions itThis rule applies to every executed code chunk in teaching materials, in every course and every teaching repository.
Set #| code-fold: on each chunk to show or true. Never set false: it removes the toggle and gains nothing, because show also keeps the code and its output together. The choice depends on one question: does the prose mention the code?
#| code-fold: show when the prose mentions the code. The choice of show holds whether the code produces output outside the chunk or not, and a chunk that defines functions or objects that later chunks use counts as producing output. The code starts open, and a reader who wants to focus on the output can hide it.#| code-fold: true when the prose does not mention the code. The code starts hidden, and a reader who wants it can open it.For example:
show.show.show. true.This rule applies to all teaching materials that name commands, in every course and every teaching repository.
r or python. A rendered code block has a copy button, and inline code does not.r, and not in braces, as in {r}, so Quarto shows the code and does not evaluate it.Example 6 (Moving an install command out of a sentence)
❌ The R tabs need
readr,dplyr,ggplot2andpurrr:install.packages(c("readr", "dplyr", "ggplot2", "purrr")).✅ The R tabs need
readr,dplyr,ggplot2andpurrr:
The package names stay inline because they sit in the sentence. The install command moves to a block, so the reader copies it with one click.
This rule applies to all teaching materials that run code, in every course and every teaching repository.
#fig- div with a caption.ariaLabel.echo: false on a cell you want students to read.A page should load and finish rendering its math in under 5 seconds, and its scroll bar should not shrink to its smallest length (Ezra, 2026-10-31). The browser typesets every formula on a page, so a page with hundreds of formulas takes seconds to finish. A page tall enough to shrink the scroll bar to its minimum leaves the reader no way to see where they are in it.
The check-page-length check in gha (gha#1377) warns when a pull request adds to a page that, with its includes expanded, has more than 600 math expressions or 5000 source lines. Those limits are proxies, since the check does not run a browser. It warns only on pages a pull request touches, so split a long page when you next edit it, and do not sweep the others.
Aim for at most about five sibling headings under one parent heading, and split or group the headings when there are more than seven (Ezra, 2026-11-02). Sibling headings are headings of the same level under the same parent. The top-level headings of a page or book chapter count as siblings under the page.
A reader who scans a list of headings holds the whole list in mind while choosing among them. Miller’s “magical number seven, plus or minus two” is the classic estimate of how many items a person can hold at once. Cowan’s reconsideration puts the capacity nearer four chunks. Those estimates concern memory, and a reader can look at a table of contents again, so the limit is a guideline and not a hard rule. Aim for five so that the guideline still holds when a few headings are added later.
The check-heading-siblings check in gha (gha#1390) warns when a pull request adds a heading to a group of more than seven siblings. It warns only on pages a pull request touches, so regroup a page when you next edit it, and do not sweep the others.
Give a heading to a point that can stand alone, and fold a section of a single short paragraph into the section it qualifies or follows (Ezra, 2026-11-02). A heading that opens a one-paragraph section adds an entry to the list of headings and gives the reader nothing to find there. This is a guideline, not a minimum length: a definition, a distinct step or a named result can be short.
{.content-hidden when-format="revealjs"}. Remove an existing wrapper from a proof when you edit the file for another reason, and do not sweep the others (Ezra, 2026-10-29).#exm- div.#exm- div and short commentary in a #rem- div, because a ::: notes div is plain text without a box on the web page.::: {#rem-name .remark .notes}. Reveal.js shows it only in speaker view, so the audience does not have to read it, and the web page shows it as a boxed remark. Readers who did not attend the lecture use the web page, so nothing is lost, and nothing is said twice. Give a long paragraph a separate short #rem- div only when the slide needs a takeaway to look at.A note sheet is a reference table of formulas with brief labels and no prose. This is one case of building every unit from smaller atomic units. Give each concept its own row. A row holds one formula and one short label.
The same rule applies to every other reference table, such as a table of notation.
Example 7 (Splitting a merged row)
❌ Row:
Dot product, sample mean, with both formulas in one cell.✅ Rows:
Dot productwith its formula, thenSample meanwith its formula.
The ❌ row makes the reader find where one concept ends and the next begins. The ✅ rows let the reader find one formula by one label.
When a term is defined on another page or in another site, state the idea in the sentence and link the term. Do not write about where the definition lives.
Students read concepts, not the structure of the site. A sentence about which notes define a term adds no content, and it goes stale when the definition moves.
The same holds inside a page. Focus on the content of the notes, not on their structure (Ezra, 2026-11-02). When a part refers back to an earlier exercise, example, or result, name the specific objects it reuses instead of pointing at the exercise as a whole. “Use the setup of Exercise 1” makes the reader reread the exercise to learn which data, variables, or model the part needs.
Name what the back-reference reuses
Keep the cross-reference so that the reader can look up the full statement, and add the names of the reused data, variables, parameters, or model. When the notes define a dataset in its own callout, link the dataset by that callout’s id instead of describing it again, and put the link on the dataset’s name. Quarto renders @dat-wcgs as “Dataset 1” in the link text, so write the dataset name as the link text, as in [WCGS data](#dat-wcgs), not “the WCGS data (@dat-wcgs)” (Ezra, 2026-11-04; see “Link the words, not a trailing citation” in chapters/citations-evidence.qmd). Prefer a self-contained sentence when the part needs only one or two of those objects.
Structure the rubric for most STEM questions as a checklist (Ezra, 2026-10-26). A grader then marks each item as earned or not, and the points are the sum of the earned items. A rubric written as a paragraph makes the grader reconstruct the items, and two graders reconstruct different items.
Example 8 (A rubric as a checklist)
❌ Grading (6 points): two for \(\vec r\), one for \(\dprod{\vx}{\vec r} = 0\), three for “orthogonal” with its link to the normal equation.
✅
The checklist shows the grader each item to mark and lets a script check that the points sum to the total.
.callout-note titled “Source”, not in running prose. Adapting another course’s material gives the details, including when a ::: notes div is a safe alternative.Complete each sentence, step, term, result and exercise, and do not leave part of it implied, merged into a neighbor, or half done. A reader should never have to finish a unit for you. Several rules in this guide are cases of this one:
When you catch yourself taking a shortcut, finish the unit instead. A shortcut saves the writer a few lines and costs every reader the work of rebuilding them.
These rules from the other chapters apply to teaching materials as to any other writing:
22 Comment every code chunk to say what and why