I can write Python. I have shipped a web application in it, with a test suite and a deployment pipeline and arguments about coverage.
I have also never finished a beginner programming course. Not one.
Not because they were too hard. Because somewhere in the first week, every single one of them asked me to solve a maths problem before it had told me what a variable was.
The genre
You know the exercises. Every course has them, in roughly this order:
print the numbers 1 to 100, but Fizz, but Buzz
is this number prime
the first n Fibonacci numbers
a pyramid of hashes, n rows tall
convert Celsius to Fahrenheit
sum every even number below one thousand
Look at what's actually being asked. The programming content of "is this number prime" is a loop and a conditional. The rest of it — why you only need to check up to the square root, why 1 isn't prime, what a factor is — is arithmetic that has nothing to do with Python and everything to do with whether you liked maths at school.
Two difficulties, stacked, with no way to tell them apart
When a learner fails that exercise, something has gone wrong. But which thing?
They might not understand for. They might not understand modulo. They might understand both perfectly and have no idea what a prime number is for. From the outside these look identical: an empty editor and somebody who has stopped.
And here is the part that actually costs the industry people. The learner diagnoses it themselves, immediately and wrongly:
I was never any good at maths. I'm probably not cut out for this.
That conclusion is false. Most professional Python is strings, dictionaries, HTTP and file handling. I have written far more dict.get than modulo in my life. But the learner has no way to know that yet, because the only programming they've been shown is maths, and they've just failed at it.
They don't bounce off programming. They bounce off arithmetic wearing programming's coat, and they never find out the difference.
So why does every course do it?
This is the part I didn't expect, and it's the reason I'm writing this rather than just complaining.
It isn't pedagogy. It's marking.
A maths exercise has exactly one right answer. is_prime(7) returns True, and there is no argument about it. You can check that with one line of test code, and it will be correct for every learner, forever.
Now try to auto-mark this instead:
Print a shopping list with a total at the bottom.
How many valid programs are there? Hundreds. Do you accept a trailing newline? Two spaces instead of one? "Total: 12" or "Total: 12.00"? A dictionary or two parallel lists? A function or straight-line code?
Every one of those is a correct answer from a beginner, and a naive string comparison fails most of them. The learner then gets marked wrong for being right, which is worse than being bored.
Maths exercises aren't in your first week because they teach well. They're in your first week because they're cheap to grade. It is a testing constraint that leaked into the curriculum, and it has been there so long that everyone mistook it for teaching.
What the alternative actually costs
I've been building a course on the other side of that trade, and the engineering is where all the cost lives.
Three rules fell out of it, and the third surprised me.
Checks are authored, not generated. Every check carries a sentence a human wrote about what was expected, in the lesson's own vocabulary. No "AssertionError: expected 'Total: 12' but got 'Total: 12.0'".
A check may show what it saw, never what it is. This is the one I had to write down in the code, because it is very tempting to break:
class CheckResult(BaseModel):
"""One check's verdict, in the only terms the learner ever sees.
`label` is authored by hand per check and `detail` carries observed
values. Check source never appears in either.
"""
label: str
passed: bool
detail: str | None = None
Show a learner the test that failed them and you have not taught them to solve the problem. You have taught them to satisfy the test, which is a different and much worse skill, and one that survives into their professional life.
Several right answers must pass. This is the expensive half. It is not a feature you add; it is a constraint on every single task you write. Some tasks get a hidden pytest case that imports what the learner wrote and calls it. Some normalise whitespace before comparing. Some can't be auto-marked at all and have to be rewritten until they can.
That is slow. 135 lessons in, I can tell you the authoring is three or four times the work of writing the maths version. That cost is the real reason the maths version won.
What goes in the first week instead
Here's a whole lesson, near the very start:
score = 10
print("score: " + score)
TypeError: can only concatenate str (not "int") to str
That's it. That's the lesson.
It teaches types, and it teaches them in the only way that sticks: the learner has already been bitten, so there is a reason to care about the answer. No arithmetic. No domain knowledge. Nothing to be bad at except the thing actually being taught.
The rule I ended up with is that where a concept exists because something doesn't work, show the failure first. A sentence about types teaches almost nothing. A TypeError with your own name on it teaches it permanently.
Where I'll concede
Maths exercises are not useless. If your goal is a computer science degree, discrete maths is load-bearing and you should meet it early. Those courses are not wrong — they are university courses doing a university course's job. The mistake is recommending them as the universal first step to somebody who wants to build a thing.
"But programming is maths." Logic, yes. Arithmetic, mostly no. I want a beginner to meet boolean logic in week one. I do not want them to meet modular arithmetic in week one, and those two have been bundled together for forty years.
I have no retention data. None. I have a design argument, one learner, and 135 lessons. Anyone telling you their pedagogy is proven after a year is selling something. If this is wrong, it'll be wrong in a way that shows up in a year, not in this post.
And the cost is real. If you are building a course and you take this advice, you are signing up for roughly four times the authoring work per lesson and a check design you will revisit repeatedly. I think it's worth it. I also understand completely why the industry decided otherwise.
This is part of LexipawsPY, a free Python course I'm building. Every lesson is free and stays free — the whole course, the drills, the hints and the solutions. It's at py.lexipaws.eu if you want a look, though fair warning: it's still being written. 135 lessons today, through loops, lists and tuples, stopping just before dictionaries.
If you learned to code fine through FizzBuzz and think I'm overthinking this, I'd genuinely like to hear it — I'm aware I might be generalising from one person who doesn't like maths.
Top comments (0)