Designing a Lesson to Master Prime Numbers

A complete walkthrough: you will design and build a lesson that teaches an introductory programming class what a prime number is, how to decide whether a number is prime, and how to write a program that does it — ending with an exercise the platform grades automatically while your students work in a terminal window inside the lesson itself.

Who this is for. Instructors. You need to know your subject and your students. You do not need to have built a lesson on this platform before, and nothing here assumes you write software for a living.

What it costs. About two hours to build the first time, most of it spent writing the teaching text — which you would have written anyway. The finished lesson is about forty-five minutes of class time.

What you need before you start.

Contents


1. What your students will be able to do

Write the objectives first. They decide everything that follows — what goes in the lesson, what order it goes in, and what the graded exercise has to ask for.

For this lesson, a student who has finished it can:

  1. State what a prime number is, and say why 1 is not one and why 2 is the only even one.
  2. Justify a shortcut. Explain why you only have to test divisors up to the square root of a number before declaring it prime — not merely apply the rule, but say why it holds.
  3. Write a working program that reads a list of numbers and decides primality for each of them.

Notice the shape of those three. The first is recall, the second is reasoning, and the third is performance — something the student produces, which you can look at. A lesson with only the first kind is a reading assignment. A lesson with only the third kind teaches technique without understanding. This lesson has one of each, and they are sequenced in that order deliberately: the reasoning objective is what turns the program from a recipe into an idea the student owns.

Objective 2 is the one worth protecting. It is easy to write a lesson where students copy a loop that stops at the square root without ever being asked why, and such students write correct programs and learn nothing durable. Section 2 sequences the lesson so the argument arrives before the code, and Section 4 designs the exercise so a student who skipped it gets a lower score than one who did not.


2. Designing the lesson before you build it

This is the part that transfers to every other lesson you will write. Do it on paper.

What a lesson is made of

Four words, and then we can stop talking about the tool:

Word What it means
Panel One page of your lesson, listed in the panel list on the left. Students move through panels in order, top to bottom. A panel holds text, images, tables, code examples, a video — whatever the material needs. The panel list tags each one by kind: plain teaching material shows a Content chip; Mission and Challenge panels show their own.
Mission A container that holds the graded work in a lesson. Think of it as the “assignment” section of a chapter.
Challenge One graded exercise inside a mission. This lesson has exactly one.
File Something bundled into a challenge for a student’s program to use or run — a starter source file, for instance — rather than a page anyone reads. A file is not a panel: it never appears in the lesson’s reading order, only inside the one challenge that owns it. Section 6 adds one.
Publishing Turning your draft into a finished lesson that can be given to a class. Until you publish, only you can see it.
Assigning Giving a published lesson to one of your class sections, with the dates you choose. This is what puts it in front of students.

That is the whole vocabulary. A lesson is a stack of panels, one of which is a mission holding a challenge with a file of its own; you publish it, then you assign it.

The sequence, and why it is that sequence

Six panels:

# Panel What it does Class time
1 What Makes a Number Prime Establishes the definition and settles the two cases students always get wrong: 1 and 2. 7 min
2 Trial Division Gives the method, then argues for the square-root shortcut. 10 min
3 Writing It in C++ Translates the method into the shape of a program, without giving away the answer. 10 min
4 Your Workbench Introduces the workspace and how to test and submit. 5 min
5 Primality Mission Holds the graded exercise; carries the brief. —
6 Prime Verdicts The exercise itself. 13 min

Three decisions are worth naming, because you will make the same three in your own lessons:

The concept comes before the method. Panel 1 does not mention divisibility testing at all. A student who reaches panel 2 already knows what question is being answered, so the algorithm reads as an answer rather than as a ritual.

The argument comes before the code. Panel 2 ends with the square-root reasoning; panel 3 opens with the program shape. If those two are swapped — code first, justification afterwards — most students never read the justification, because by then they have something that works. Sequence protects objective 2.

The code panel withholds the answer. Panel 3 gives the reading loop and the shape of the function, and leaves the primality test itself as a comment. The student has everything structural and nothing conceptual. This is the single most common authoring mistake in programming lessons: supplying a complete worked example one panel above the exercise that asks for it, and then wondering why every submission looks the same.

Where to stop and talk

Two natural pauses, if you are teaching this live rather than assigning it as homework:

The pattern underneath

Strip the subject out and the design is: concept → method → the shape of a solution → workspace → graded production. That pattern fits any lesson where students must produce something at the end. Section 9 comes back to it.


3. Building the teaching panels

Now open the Studio.

Getting set up

If you have not used the Studio before, work through Getting to Know Instructor Studio first — it covers signing in, choosing your spaces, and the editor’s controls, all of which this tutorial assumes from here on.

  1. Open the Instructor Studio and sign in.
  2. Choose File ▸ New… to start an empty lesson.
  3. Immediately choose File ▸ Save as…, pick a folder, and name the lesson Prime Numbers in C++. Name it now: the name becomes the lesson’s identity everywhere else, and setting it first saves renaming later.

Save often as you work — the keyboard shortcut is the usual one for your platform. The Studio will not let you publish a lesson with unsaved changes, which is a feature: what students receive is always exactly what you last reviewed.

How the editor works

You type on the left and see the finished page on the right, updating as you type — in a lightly marked-up text format where # starts a heading, **bold** is bold, and tables are drawn with pipes. Right-click anywhere in the text for the menus that add all of this for you. The tour walks through them; this tutorial names the ones it needs as it reaches them.

One mark the tour does not cover, because this lesson is the reason to know it: a block fenced with three backticks is shown as a code example in a monospaced box, except a fence marked ` ```mermaid `, which draws a flowchart or diagram instead. We use that in Panel 2.

To add a panel, use the + Panel button below the panel list and choose New Content. To rename a panel, double-click it in the list. To reorder panels, drag them.

Panel 1 — What Makes a Number Prime

Add a content panel, rename it What Makes a Number Prime, and type (or paste) this:

# What Makes a Number Prime

A **prime number** is a whole number greater than 1 whose only positive
divisors are 1 and itself. A whole number greater than 1 with any other
divisor is **composite**.

| n | positive divisors | verdict |
|---|---|---|
| 1 | 1 | neither — a prime must be greater than 1 |
| 2 | 1, 2 | prime |
| 9 | 1, 3, 9 | composite (3 × 3) |
| 13 | 1, 13 | prime |
| 91 | 1, 7, 13, 91 | composite (7 × 13) |

## Why 1 is not prime

1 has exactly one positive divisor — itself. Every rule that makes
primes useful depends on each number having one factorization into
primes, and admitting 1 would give every number endlessly many
(`6 = 2 × 3 = 1 × 2 × 3 = 1 × 1 × 2 × 3`). So 1 is neither prime nor
composite.

## The only even prime

2 is prime: its divisors are 1 and 2. Every larger even number has 2 as
a third divisor, so it is composite. That single fact halves the work in
the next panel — after checking 2, you never test another even divisor.

:::{note}
Try it by hand before reading on: which of 51, 53, 57 and 59 are prime?
:::

The block beginning :::{note} produces a highlighted aside on the finished page. Right-click and open Insert ▸ Box for four ready-made kinds — Note, Warning, Exercise, and a Dropdown that hides its content until a student expands it — or the top-level Call outs menu for five more, styled ones. Use them deliberately: a note is where you put the thing you would say out loud.

Two authoring choices in this panel are doing real work. The table shows verdicts with their divisors, so a student can check the claim rather than accept it. And the closing note gives away that 91 is composite one panel before panel 2 traces it, which makes the trace feel like a confirmation instead of a revelation.

Panel 2 — Trial Division

Add another content panel, rename it Trial Division:

# Trial Division

The direct test for primality is **trial division**: try every candidate
divisor and see whether any of them divides $n$ evenly.

```text
is_prime(n):
    if n < 2:              return false
    if n is even:          return n == 2
    for d = 3, 5, 7, ... while d * d <= n:
        if n mod d == 0:   return false
    return true
```

## Why you may stop at the square root

Suppose $n$ is composite, so $n = a \times b$ with $1 < a \le b < n$. If
both factors were larger than $\sqrt{n}$, their product would exceed
$\sqrt{n} \times \sqrt{n} = n$ — a contradiction. So the smaller factor
satisfies $a \le \sqrt{n}$.

Every composite therefore reveals a divisor at or below its square root.
Finding none up to $\sqrt{n}$ proves the number is prime; there is
nothing above the square root left to check.

:::{tip}
Write the loop condition as `d * d <= n` rather than `d <= sqrt(n)`. It
stays in whole numbers, so there is no rounding to get wrong.
:::

## Two traces

**91.** Test 2 — no (91 is odd). Test 3 — no. Test 5 — no. Test 7 —
yes: $91 = 7 \times 13$. **Composite**, after four tests.

**97.** Test 2, 3, 5, 7 — none divide. The next candidate is 9, but
$9 \times 9 = 81 \le 97$ while $11 \times 11 = 121 > 97$, so the loop
ends after 9. **Prime**, after five tests — not 95.

## The procedure, as a flowchart

The pseudocode above and this diagram are the same procedure, read two
ways. Trace 1, 2, 4, 9, 25 and 7919 through it by hand — each one takes
a different path to its exit — before writing the C++ version in the
next panel.

```mermaid
flowchart TD
    A(["Read a number, n"]) --> B{"n at least 2?"}
    B -- no --> NP(["Not prime"])
    B -- yes --> C{"n even?"}
    C -- yes --> D{"n equal to 2?"}
    D -- yes --> P(["Prime"])
    D -- no --> NP
    C -- no --> E["Start with divisor d = 3"]
    E --> F{"d times d greater than n?"}
    F -- yes --> P
    F -- no --> G{"does d divide n?"}
    G -- yes --> NP
    G -- no --> H["d = d + 2"]
    H --> F
```

Mathematics written between dollar signs is typeset properly on the finished page, so $\sqrt{n}$ appears as a real square-root sign. Check the preview pane as you type it: if a formula does not render, a bracket is unbalanced somewhere in it.

The :::{tip} box above isn’t on either the Insert ▸ Box or the Call outs menu — type it by hand, as it stands in the copy-block — but it is valid MyST and renders exactly the way Note and Warning do.

The two traces at the end are not decoration. “Prime, after five tests — not 95” is the whole point of the panel expressed as a number a student can feel. Whatever your subject, look for the sentence that turns your abstract argument into a quantity, and put it last.

The algorithm, drawn

Panel 2’s copy-block above already ends with a flowchart of the same procedure the pseudocode gives — the Instructor Studio’s lesson renderer draws Mermaid diagrams natively, so a fenced ` ```mermaid ` block in a panel’s text becomes a picture on the finished page, right alongside the pseudocode above it and the code panel that follows it. Next time you add one from scratch, right-click and choose Insert ▸ Mermaid diagram for a starter block instead of typing the fence by hand. There is no export-to-image step: the diagram is part of the panel, and students see it.

Read the diagram once yourself before you teach the panel: the shape of the decision tree is what students are actually being asked to reproduce in code, and the four exits — two “not prime”, two “prime” — are where every mistake in Section 4’s diagnostic cases lands. Each branch is one of the fixed cases from Section 4: 1 exits at the first decision, 2 and 4 at the even-number decisions, 9 and 25 inside the divisor loop, and 7919 at the loop’s stopping rule — the one students get wrong when they half-remember the square-root shortcut. A student whose program takes the wrong branch has told you exactly which box they misread.

Panel 3 — Writing It in C++

Add a content panel, rename it Writing It in C++:

# Writing It in C++

Your program reads from **standard input** and writes to **standard
output**. It takes no arguments and opens no files — you will be given
numbers to read, and whatever you print is your answer.

## Reading every number until the input runs out

```cpp
#include <iostream>

int main() {
    int n;
    while (std::cin >> n) {
        // one verdict per number, in the order they arrive
    }
    return 0;
}
```

`std::cin >> n` is false when there is nothing left to read, so the loop
ends by itself. Do not ask how many numbers there are first — you are
not told, and you do not need to know.

## The skeleton to fill in

```cpp
#include <iostream>

bool isPrime(int n) {
    // TODO: apply trial division from the previous panel.
    return false;
}

int main() {
    int n;
    while (std::cin >> n) {
        std::cout << (isPrime(n) ? "true" : "false") << '\n';
    }
    return 0;
}
```

## Printing exactly what is expected

One verdict per line, lowercase, and nothing else on the line — no
prompts, no "Enter a number:", no summary at the end.

:::{warning}
Anything your program prints that is not a verdict pushes every later
line out of place, and the whole answer is scored against the wrong
lines.
:::

The TODO comment is the design decision from Section 2 made concrete. Resist filling it in.

Panel 4 — Your Workbench

Add the fourth content panel, rename it Your Workbench. Write the text now; you will add the terminal itself in Section 6, once the exercise exists for it to point at.

# Your Workbench

The terminal below is a real computer. When you open it, it puts you
straight into the folder for this exercise — the one holding
`primes.cpp`.

## Editing your program

Type `emacs primes.cpp` to open the file. Three key combinations get you
through:

| Keys | Does |
|---|---|
| `Ctrl-x Ctrl-s` | save |
| `Ctrl-x Ctrl-c` | exit |
| `Ctrl-g` | cancel whatever you started by accident |

`nano primes.cpp` works too, if you prefer it.

## Test, then submit

```
merlin test
```

builds and runs your program against a fresh set of numbers and shows
you how it did. Nothing is recorded — run it as often as you like.

```
merlin submit
```

records an official attempt and shows your score. Test until it is
clean, then submit.

The glossary

Select the words prime number, composite and trial division in your text, right-click the selection, and choose Add term to glossary…. The first definition creates a Glossary panel automatically, and every use of the word in every panel becomes a link to it.

Two reasons this is worth the two minutes it takes. Students who have forgotten a word get it back without leaving the page, and — more usefully to you — writing the definitions surfaces vocabulary you have been using loosely. If you find you cannot define composite in one sentence without using prime, that is a sequencing problem the glossary just caught for you.


4. Designing the exercise

Back to paper. This section is about what makes a graded programming exercise worth setting; Section 5 builds the one we design here.

The exercise generates its own numbers

The platform runs each student’s program against a list of numbers that is made fresh for every attempt. You write a short script — supplied below — that produces the numbers and the correct answers together.

Two consequences that change how you teach:

The output contract has to be unambiguous

Before anything else, decide exactly what a correct answer looks like, and state it in the question in a way that cannot be read two ways. Ours: one verdict per line, lowercase, in the same order as the input, nothing else on the line.

An ambiguous contract turns your grading into a lottery and your office hours into a queue of students whose logic was right. Say what is printed, in what order, and what must not appear. Then say it again in the panel that teaches the code, which is what the warning box in panel 3 is for.

Choose the awkward cases on purpose

The lesson supplies twenty numbers per attempt: seven fixed cases you choose, followed by thirteen drawn at random. The random ones give coverage. The seven fixed ones are where the teaching happens, because each is aimed at one specific misconception:

Number The mistake it catches
1 Treating “has no divisor other than itself” as the whole definition, and calling 1 prime.
2 Rejecting all even numbers, and losing the only even prime.
3 The smallest odd prime — catches a loop that starts testing at the wrong place.
4 The smallest even composite — catches the reverse of the 2 error.
9 Assuming odd means prime. This one catches more students than every other case combined.
25 An odd square with an odd factor — catches a test that only ever tries 2 and 3.
7919 A large prime — catches a loop that stops too early, which is exactly the failure of a student who half-remembered the square-root rule.

That last row is the design working. A student who skipped objective 2 and copied a stopping condition without understanding it fails on 7919 and passes everything else. The score does not just say wrong — it points at the idea that is missing.

This is the technique worth stealing: for each misconception you expect, put one case in the fixed list that only that misconception fails.

Partial credit is a teaching signal

The platform compares the student’s output to the answer key line by line and awards the fraction of lines that matched. A program that gets nineteen of twenty right scores 0.95, not zero.

Set expectations accordingly when you introduce the exercise. A student whose score jumps from 0.60 to 0.95 by fixing one case has just learned something specific, and knows what it was. If your subject genuinely requires all-or-nothing correctness, say so in the question — but for a first programming exercise, the graded feedback loop is the point.


5. Building the exercise

Create the mission

Use + Panel ▸ New Mission. Name it Primality Mission and write the brief in its description editor:

Write a program that decides primality for a list of numbers you have
never seen. You get twenty numbers between 1 and 10000 — some of them
deliberately awkward — and you print one verdict per number.

Work in the Workbench above: edit `primes.cpp`, run `merlin test` until
it is clean, then `merlin submit`.

Create the challenge

With the mission selected, right-click the panel list and choose New challenge. Rename the new panel Prime Verdicts.

Challenge names have to be different from one another within a lesson, and they show up in your gradebook — so name them for what they ask, not “Challenge 1”.

Choose how it is graded

The challenge panel offers a Grading approach with two settings:

Choose Program · orchestrated. The answer-key section disappears and a program-grading section takes its place.

Write the question

In the challenge’s question editor:

Read numbers from the input, one per line, until the input runs out. For
each number print `true` if it is prime and `false` if it is not — one
verdict per line, in the same order the numbers arrived, lowercase,
nothing else on the line.

Your program is run once against **twenty numbers**: seven fixed cases
chosen to catch the usual mistakes, and thirteen drawn at random from 1
to 10000. Every line must match to pass; a partial score is the fraction
of lines you got right.

Submit your work as `primes.cpp`.

Telling students that seven cases are chosen deliberately is itself instruction. It tells them that guessing at the easy middle of the range will not carry them, and that re-reading panel 1 might.

Fill in the grading settings

Setting Value What it means
Language C++ (g++, C++17) Which language the student writes in, and which compiler builds it.
Submission file primes.cpp The file the student edits and submits. Name it for the exercise.
Comparator Boolean per line How answers are compared. Boolean per line accepts true/false, 1/0 and yes/no as the same answer, so a student is not punished for a reasonable choice of wording.
Ignore whitespace within a line ✔ Trailing spaces stop being a way to fail.
Ignore case ✔ True and TRUE are accepted.

If something in this section is inconsistent — a file name that does not match the language, for instance — a warning appears beside the heading and stays until it is fixed. The lesson cannot be published while it is showing.

Supply the number-maker

Below the settings is a box holding a short script that makes the numbers and the answer key for each attempt. Replace what is there with this:

#!/bin/sh
python3 - <<'PY'
import random

def is_prime(n):
    if n < 2:
        return False
    if n % 2 == 0:
        return n == 2
    i = 3
    while i * i <= n:
        if n % i == 0:
            return False
        i += 2
    return True

# The seven fixed cases from the lesson design, then thirteen at random.
edge = [1, 2, 3, 4, 9, 25, 7919]
numbers = edge + [random.randint(1, 10000) for _ in range(13)]

open('./input', 'w').write('\n'.join(str(n) for n in numbers) + '\n')
open('./expected', 'w').write('\n'.join('true' if is_prime(n) else 'false' for n in numbers) + '\n')
PY

You do not have to be able to write this to use it, but you should know what the four moving parts are, because those are the parts you will change:

Save the lesson.


6. Giving students a workspace

Students need somewhere to write the program, and something waiting for them when they get there. That is two steps: give the challenge its starter file, then give the lesson a terminal that opens where it lives.

Step 1 — give the challenge its starter file

Select the Prime Verdicts panel again. Below the grading settings you filled in during Section 5 is a Workbench section — the files a student finds already in their challenge directory, on any challenge, not only a program-graded one. This is the File from Section 2’s vocabulary: it lives inside this one challenge, never in the panel list, and no student reads it as a page.

Choose + Add text file. A new entry appears named starter.txt; click its name and change it to primes.cpp — the file panel 4 already tells the student is waiting for them.

#include <iostream>

bool isPrime(int n) {
    // TODO: apply trial division from the previous panel.
    return false;
}

int main() {
    int n;
    while (std::cin >> n) {
        std::cout << (isPrime(n) ? "true" : "false") << '\n';
    }
    return 0;
}

Paste it into the entry’s text box — it is the same skeleton you already wrote into panel 3, not a second one to maintain. Leave executable unchecked; a C++ source file the student compiles is not a script they run directly. Watch the small summary under the file list as you go: it counts entries and reports their combined size, measured as you type — the same measurement the platform repeats when you publish and again when a student’s directory is prepared, so the two can never disagree about what was authored.

Save the lesson.

This is the step the rest of this tutorial was missing. Without it, a lesson built exactly as Sections 1–5 describe would open a terminal with nothing in it — the promise in Section 8, “primes.cpp waiting,” would not yet be true. It is now: what you just placed here is the file a student’s emacs primes.cpp opens, already holding the skeleton, before they have written a line of their own.

Step 2 — give students a terminal that opens there

Select the Your Workbench panel, click into its text on the left side of the editor, and right-click there — not the panel list. Choose Insert ▸ Merlin Terminal ▸ Challenge, then pick Prime Verdicts from the picker that appears. That is the whole step. When you publish, the platform arranges for the terminal to open in the right place for whichever student is looking at it.

:::{note} The identifier this step writes for you is minted once, the moment you first created the Prime Verdicts panel, from whatever its name was at that moment — it does not update if you rename the panel afterward. That is deliberate: it is a stable internal reference, not a display name, and nothing about renaming a challenge later needs to know or change it. :::

This is worth a sentence of appreciation in class. Setting up a compiler is a substantial obstacle for beginners, and it is not what you are teaching. Here there is nothing to install and nothing to configure — the first thing a student does in this lesson is edit a program that is already there waiting for them.


7. Publishing and assigning

Publish

Save, then choose File ▸ Publish…. Four steps:

  1. Publishing Space — where the finished lesson goes.
  2. Course — which course it belongs to.
  3. Lesson — choose New entry the first time. Publishing again later updates the same entry rather than creating a second one.
  4. Checks — the Studio verifies the lesson before it goes out: that every challenge has a question, that names are unique and meaningful, and that choice questions are well formed. Everything must pass.

Then publish. The lesson is now finished work, ready to be given to a class. Publishing does not put it in front of anyone yet.

Assign

Choose File ▸ Assign…. Three steps:

  1. Space — normally the same one you published to.
  2. Section — the class that gets it. Only sections of this lesson’s own course are offered.
  3. The assignment itself — the name students see, when it becomes available, when it is due, an optional late window, and whether it is visible now or held as a draft.

Set the visibility to draft if you are preparing ahead of time; students see nothing until you publish the assignment.

The same published lesson can be assigned to several sections with different dates. Write once, teach four times.


8. What the student experiences

Worth knowing, so you know what you are asking for.

The student opens the lesson from their assignment list and reads through the four teaching panels. At the Workbench panel there is a terminal, already in the folder for this exercise, with primes.cpp waiting in it. They open the file, write their program, and run:

merlin test

which builds it, runs it against a freshly-made list of twenty numbers, and reports how it did — recording nothing. They fix, and run it again. When it looks right:

merlin submit

records the attempt and shows the score. A perfect run scores full marks; a program that mishandles 1, 2 or 9 scores the fraction of lines it got right, and the student can see which idea is missing.

If a student reports the terminal saying the folder is not ready, the lesson has not been assigned to their section yet, or they are not on its roster.


9. Adapting this lesson to your own material

The design in Section 2 — concept → method → the shape of a solution → workspace → graded production — is not about primes. Three ways to move it.

Same lesson, different language. Change the Language setting to Python 3 and the Submission file to primes.py, and rewrite panel 3 for Python. Nothing else moves: the question, the number-maker, the comparison settings and the workspace are all unchanged, because they describe the task, not the language.

Same shape, different topic. Any exercise where a program reads input and prints one answer per line drops straight into this structure — greatest common divisors, Roman numeral conversion, leap years, word counts, temperature conversion. What you rewrite is the four teaching panels, the is_prime function inside the number-maker (it becomes whatever “correct” means for your task), and the seven fixed cases. Spend your time on the fixed cases; that is where the diagnostic power lives.

Same topic, different level. For a class that has already met primality, drop panel 1 to a reminder, and change the range in the number-maker from 10000 to something large enough that a slow method times out — the lesson then teaches efficiency rather than definition. For a younger class, narrow the range to 100, cut the square-root argument to a demonstration on 36, and let objective 2 go.

That last option is a real choice, not a compromise. Objective 2 is the most demanding thing in this lesson. Dropping it deliberately for a class that is not ready is better than keeping it and having it skimmed.


Before you teach it