PRIMM is a five-step routine for learning from code examples. Instead of jumping straight to writing your own programs, you start by reading and understanding someone else's working code, then gradually take more control until you are building something yourself. The steps are: Predict, Run, Investigate, Modify, Make. Each step builds on the last. The sequence is not arbitrary — it matches how understanding actually develops.
This article is part of the Code Reading, Tracing & Prediction section. The techniques covered in How to Read Code, Trace Tables, and Tracing Loops are all used inside PRIMM — specifically in the Predict and Investigate steps. If those articles are new to you, read them first.
| Level | What it covers | When to read it |
|---|---|---|
| Basic idea | What each step means and what you do in it | Right now. |
| At a deeper level | Why the sequence works; what each step is actually training; common mistakes at each step | Once you have completed one full PRIMM cycle on your own. |
| At the deepest level | The research behind PRIMM and how it connects to broader ideas about learning | When you are curious about why the course is structured the way it is. |
1 The Five Steps
PRIMM gives you a structured way to move from reading code you did not write to producing code of your own. Each letter is a step.
Read the program. Without running it, write down exactly what you think it will do. Predict the output line by line. Commit to a specific answer before the program runs.
Run the program. Compare what actually happened to what you predicted. Mark every place where reality differed from your prediction. Do not skip this comparison — it is the most important moment in the whole cycle.
Dig into the code to understand why it behaves the way it does. Use trace tables, add print() statements, change individual values, ask questions. Your goal is to be able to explain every line.
Make targeted changes to the existing program. Change one thing at a time. Predict what your change will do before running it. The code is still the starting point — you are adapting it, not replacing it.
Use what you have learned to build something new. The Make step is not a copy of the example — it is a new program that applies the same ideas in a different context. The example is now a reference, not a template to copy from.
Notice what you are not doing at each step. You are not writing code from scratch in the first four steps. You are reading, comparing, questioning, and adapting. Production — writing new code — comes last, after understanding is established. This ordering is deliberate and important.
| Step | What you are doing | What you are not doing |
|---|---|---|
| Predict | Committing to a specific answer before running | Guessing vaguely, or skipping to Run without a written prediction |
| Run | Comparing actual output to your prediction, marking discrepancies | Just watching the output scroll past without comparing |
| Investigate | Finding out why by tracing, testing, and questioning | Accepting that it works without understanding why |
| Modify | Making one deliberate change at a time, predicting the effect first | Changing multiple things at once, or copying the code into a new file |
| Make | Building something new that uses the same ideas in a different context | Copying the example and changing variable names |
PRIMM was developed by Sue Sentance and colleagues as a structured approach to programming pedagogy, drawing on research into how novices learn to program. The key insight is that reading comprehension and writing production are separate skills that develop at different rates. Beginners who are asked to write code before they can reliably read it are working in a gap — they lack the foundation needed to make deliberate decisions.
The sequence from reading to making mirrors what cognitive scientists call worked example fading: you start with a fully worked example (Predict, Run, Investigate), transition to partial production (Modify), and finally move to independent production (Make). Research in mathematics and problem solving consistently finds that this sequence produces faster and more durable learning than jumping straight to independent practice.
2 Step 1: Predict
Predicting means writing down exactly what you think a program will output before running it. Not a vague sense of what it might do — a specific, written prediction that you can compare to reality.
Here is the program used throughout this article. Read it now. Before looking further, write down what you think it prints.
def greet(name, times):
for i in range(times):
print("Hello,", name)
greet("Alex", 3)
greet("Sam", 1)Write your prediction. Then continue reading.
A good prediction is precise. "It prints Hello a few times" is not a prediction — it is a vague impression. A precise prediction states exactly which lines appear in exactly which order:
Hello, Alex Hello, Alex Hello, Alex Hello, Sam
To get to that precision, use the trace skills from the other articles in this section. Trace the function call, follow the loop, count the iterations. Write the output line by line.
If you are not sure what the program will do, that is fine — write your best guess and note what you are unsure about. Uncertainty is not a reason to skip the prediction. It is the reason to make one.
A prediction held only in your head is not a prediction — it is a feeling. Feelings adjust themselves after the fact. A written prediction cannot. Writing forces specificity, and the comparison between your written prediction and the actual output is where the learning happens. If you skip the writing, you skip the learning.
Predicting before running is a form of retrieval practice — you are actively constructing an answer from what you know, rather than passively reading. Research in cognitive psychology consistently finds that retrieval practice produces stronger and more durable memory than re-reading or reviewing. The prediction step forces you to construct a model of the program's behaviour; the Run step tests that model. The act of testing your own model — and being wrong — is significantly more effective at building understanding than just reading the explanation of how the code works.
This is also why the Predict step must come before the Run step, not after. Predicting after you have already seen the output is trivially easy and teaches nothing. The difficulty — and the value — of prediction comes from the uncertainty.
3 Step 2: Run
Run the program. Then compare the actual output to your prediction.
The output of the example program:
Hello, Alex Hello, Alex Hello, Alex Hello, Sam
Place your prediction next to the actual output and mark every difference. Did you get the number of lines right? Did you predict the comma and the space correctly? Did you predict that greet("Sam", 1) would print exactly one line?
If your prediction matched exactly, you understood the program. If it did not, you have found a gap in your understanding — and you know exactly where to look.
The Run step is not about running the program. Any computer can do that. It is about the comparison. Here are the three things to do after the program runs:
- Mark each line of output as predicted or not predicted. A tick for lines that matched. A cross for lines that did not appear or appeared differently.
- Count the discrepancies. One wrong line is different from five wrong lines. The number tells you how confident your mental model was.
- Write a one-sentence note on each discrepancy. "I thought the loop ran four times but it ran three." or "I forgot the comma in the print statement." This note becomes the starting point for Investigate.
Once the program has run, your prediction is fixed. Do not go back and edit it to match the output. The gap between prediction and output is information. Erasing the gap erases the information.
The moment of discovering a discrepancy between prediction and output is what researchers call desirable difficulty — a productive challenge that feels harder than just reading an explanation but results in stronger learning. Being wrong, and then understanding why you were wrong, restructures your mental model in a way that simply reading correct information does not.
This is why the Run step must immediately follow the Predict step while the prediction is still fresh. The longer the gap, the weaker the comparison. In classroom practice, the ideal flow is: write prediction (2–3 minutes), run the program (30 seconds), compare immediately (1–2 minutes). The whole cycle should take under 10 minutes for a short program.
4 Step 3: Investigate
Investigate means finding out why the program behaves the way it does — and why your prediction was right or wrong where it was.
You are not trying to fix anything. The program works correctly. You are trying to understand it well enough to explain every line to someone who has never seen it.
Three tools for investigating:
- Trace table. Work through the program line by line, tracking variable values.
- Print statements. Add
print()calls inside the program to see what is happening at a specific moment. Remove them after. - Change one value. What happens if
timesis 0? What happens ifnameis an empty string? Changing values and predicting the effect is itself a form of investigation.
For the example program, Investigate might look like this:
Question from the Run step: Why does greet("Alex", 3) print three lines?
Investigation: The function has a for loop using range(times). When times is 3, range(3) produces 0, 1, 2 — three values. The loop body runs once for each value, printing one line each time. Three values, three lines.
Question: What is i used for inside the loop body?
Investigation: It is not used at all. The loop body only uses name. The variable i exists only to count iterations — its value is never referenced inside the body. This is a common pattern: a loop variable used purely as a counter.
Question: What would happen if I called greet("Alex", 0)?
Investigation: range(0) produces an empty sequence. The loop body never runs. The function prints nothing. Testing this confirms it.
"Why does this line run?" and "What would happen if I changed this to X?" are the two most productive questions in the Investigate step. Questions that start with "how do I fix" are for debugging, not investigation. The program is not broken. You are exploring it.
The Investigate step is where surface reading becomes structural understanding. You move from knowing what the program does (visible from the Run step) to knowing why it is structured the way it is and what would happen if it were structured differently. This kind of structural knowledge is what allows you to adapt and reuse patterns in new contexts — which is exactly what the Make step requires.
Adding print() statements to investigate a program's behaviour is a technique used at every level of programming. Professional developers do it routinely. It is not a beginner's crutch — it is a legitimate debugging and investigation technique. The difference between a beginner and an expert is not that experts never use print() for investigation; it is that experts know where to put them and what to look for.
5 Step 4: Modify
Modify means making deliberate changes to the existing program. You are not writing something new. You are adapting what is already there.
The rules for the Modify step:
- Change one thing at a time.
- Predict what your change will do before running it.
- Run and compare, just as you did in the Predict and Run steps.
- Understand the effect before making the next change.
Some possible modifications for the example program:
- Change
"Hello,"to"Good morning,". - Change the second call to
greet("Sam", 4). - Add a third call:
greet("everyone", 2). - Add a
print("---")line inside the function so a separator appears after each greeting block.
Each modification is a small experiment. You have a hypothesis ("if I change X to Y, the output will be Z"), you test it, and you compare. This is the scientific method applied to code.
The most important discipline in the Modify step is changing one thing at a time. Changing two things simultaneously means you cannot be sure which change caused the effect you observed. If the output is wrong, you do not know which change to undo. One change at a time is not slower — it is faster, because you always know what caused what.
Modifications should get progressively more ambitious. Start with something trivial (changing a string value). Move toward something structural (changing what the function does). Here is a structural modification for the example:
Modification task: Change the function so it prints the greeting in uppercase.
def greet(name, times):
for i in range(times):
print("Hello,", name.upper())Before running: what will this print? The name will appear in uppercase. "Alex" becomes "ALEX". Predict the exact output, then run and check.
In the Modify step, the original program is your foundation. You are making targeted edits to an existing structure, not building from scratch. If you find yourself deleting large sections of the program and rewriting them, you have jumped ahead to Make. Back up. Restore the original and make smaller, more deliberate changes.
The Modify step is where transfer begins. You are no longer just reading a program — you are making decisions about how to change it to meet new requirements. This requires understanding not just what the code does, but what the code is for and which parts are essential to its purpose versus which parts are arbitrary choices.
For example: in the greet function, the string "Hello," is an arbitrary choice — it could be anything. The structure of the loop is essential — removing it would break the function's core behaviour. Knowing which parts are essential and which are arbitrary is exactly the knowledge the Investigate step was building toward.
6 Step 5: Make
Make means building something new. You are not modifying the example program any more. You are writing a new program that uses the same programming idea — in this case, a function with a loop inside it — applied to a different problem.
The example program demonstrated: a function that takes parameters, uses a loop to repeat an action, and produces output. A Make task using those same ideas might be:
- Write a function called
countdownthat takes a number and prints each number from that number down to 1, one per line. - Write a function called
print_borderthat takes a character and a width and prints that character repeated that many times. - Write a function called
repeat_linethat takes a message and a number and prints the message that many times, each time preceded by the line number.
The example is now a reference you can look at — not a template to copy. Your new program solves a different problem.
The Make step is where you find out whether you understood the example or just memorised it. A student who memorised the example will find it hard to write a function with a different purpose. A student who understood the pattern — function takes parameters, loop uses a parameter to control repetition, body does something each time — will find the Make step much more straightforward.
Here is what the Make step is not:
- It is not copying the example program into a new file and changing the variable names.
- It is not adding more features to the example program.
- It is not asking AI to write a new program based on the example.
It is also not starting from a completely blank page with no support. You may look at the example for reference. You may look at the Investigate notes you made. What you must not do is copy code without understanding it.
In the Make step, keep the example open in one window and your new program in another. Referring to the example to remind yourself of syntax is legitimate. Copying lines from the example into your new program and then adjusting them is not — unless you can explain why each copied line is doing what you need it to do.
The Make step produces evidence. In this course, oral desk checks and project defenses ask you to explain code you wrote. If your code came from copying and adjusting rather than understanding and producing, the defense will reveal it — not as a punishment, but because the questions assume you can explain every decision. A Make step done well means you can answer "why did you write it this way?" for every line, because every line came from a decision you made with understanding.
The Make step is also where the PRIMM cycle can restart. If your new program uses a pattern or technique you do not fully understand, you now have a new example to run PRIMM on. PRIMM is not a one-time sequence — it is a repeating cycle that applies whenever you encounter code that is beyond your current understanding.
7 PRIMM and AI
PRIMM applies directly to AI-generated code. When an AI produces code for you, that code is an example — just like any other example. Before you use it, run PRIMM on it.
Predict what it does before running it. Run it and compare. Investigate until you can explain every line. Modify it to fit your actual needs. Only then is it ready to be part of your work.
Code you cannot predict, investigate, and explain is code you do not own. If you are asked to explain it in a desk check or a defense, you will not be able to. PRIMM is the process that turns AI-generated code into code you actually understand.
AI-generated code has a particular risk: it looks correct. It is usually syntactically valid, runs without errors, and produces plausible-looking output. This makes it more dangerous to accept without investigation, not less — because the usual signals that you do not understand something (errors, crashes, wrong output) may not appear.
The Investigate step is especially important with AI-generated code. Ask:
- Does every line do what I think it does?
- Are there any lines I cannot explain?
- Does this program handle edge cases — empty input, zero, a very large number — correctly?
- Is there a simpler way to write this that I would understand more easily?
If there are lines you cannot explain, do not submit the code. Either investigate further, ask the AI to explain those specific lines, or rewrite those parts in a way you understand. In this course, submitting code you cannot explain is not a shortcut — it is a gap that will be found in the oral defense.
In Mode 1, PRIMM is done with examples your teacher provides, with no AI involvement. In Mode 2, you may ask AI to explain a line you do not understand during the Investigate step — but you must then close the AI and write the explanation in your own words. In Mode 3, AI may produce code, but you are responsible for running a full PRIMM cycle on anything you submit. The mode changes what AI you have access to. The responsibility to understand does not change.
AI coding tools are trained on vast amounts of existing code and can produce working programs for many standard problems. What they cannot do is verify that the output matches your actual intent, or that edge cases are handled correctly, or that the approach is appropriate for your specific constraints. These require human judgment — and human judgment requires understanding.
PRIMM provides the structure for developing that judgment. A programmer who applies PRIMM to AI output is using AI as a teaching resource — learning from examples the AI produces, then making those ideas their own. A programmer who copies AI output without running PRIMM is not learning from it. The difference compounds over time: the first programmer's understanding grows with each example; the second programmer's dependency grows instead.
8 A Complete PRIMM Walkthrough
Here is a complete PRIMM cycle on a new example, showing what each step looks like in practice.
def largest(numbers):
best = numbers[0]
for n in numbers:
if n > best:
best = n
return best
result = largest([4, 9, 2, 7, 1])
print(result)Predict
The function takes a list. It sets best to the first item (4). It loops through every item. If an item is greater than best, it replaces best. At the end, it returns best.
Trace: best starts at 4. n=4: 4 > 4 is False. n=9: 9 > 4 is True, best becomes 9. n=2: 2 > 9 is False. n=7: 7 > 9 is False. n=1: 1 > 9 is False. Return 9.
Prediction: 9
Run
Output: 9. Prediction matched.
Investigate
Why does best start at numbers[0] rather than 0? Because if all numbers in the list were negative, starting at 0 would give the wrong answer — 0 would appear to be the largest, but it is not in the list at all. Starting at the first element guarantees best is always a real value from the list.
What happens if the list has only one item? best is set to that item. The loop runs once, comparing the item to itself — which is not greater than itself, so best does not change. The function returns the single item. Correct.
What happens if the list is empty? numbers[0] would raise an IndexError. The function has no protection against an empty list. This is a limitation worth noting.
Modify
Task: change the function so it finds the smallest value instead of the largest.
Prediction: change if n > best to if n < best. With the list [4, 9, 2, 7, 1], the result should be 1.
def smallest(numbers):
best = numbers[0]
for n in numbers:
if n < best:
best = n
return bestRun: output is 1. Prediction matched.
Make
Task: write a function called average that takes a list of numbers and returns their average. Use the same pattern of starting with an initial value, looping through the list, and accumulating a result, then returning it.
def average(numbers):
total = 0
for n in numbers:
total = total + n
return total / len(numbers)
print(average([4, 9, 2, 7, 1]))The structure is recognisably the same — a function, an initial value set before the loop, a loop through the list, a return statement — but the purpose is different and the accumulation logic has changed. This is a successful Make step.
- Name the five steps of PRIMM in order and give one sentence describing what you do in each step.
- Why must the Predict step come before the Run step? What is lost if you run first and then write a prediction?
- A student runs the program during the Run step and says, "Oh, I see — it prints those lines because of the loop." They move on to Investigate. What is wrong with this, and what should they have done instead?
- During the Investigate step of the worked example in Section 8, three questions were asked and answered. Write one additional investigation question about the
largestfunction that was not covered, and answer it. - A student in the Modify step changes three things at once, runs the program, and the output is wrong. What problem do they now have that they would not have had if they changed one thing at a time?
Here is a short program. Run a complete PRIMM cycle on it. Show your prediction, note whether it matched, write two investigation questions and their answers, describe one modification you made, and describe a Make task you could attempt next.
def repeat(word, n): result = "" for i in range(n): result = result + word return result print(repeat("ha", 3)) print(repeat("na", 4) + " Batman!")- In your own words, explain why running PRIMM on AI-generated code matters — even when that code runs without errors and produces correct-looking output.