Why I ran this gauntlet
Most engineers pick up a new language by building something they already know how to build in a language they already know. That's efficient, but it also lets you route around the parts of the language you haven't internalized yet — you reach for familiar patterns instead of learning the idiomatic ones. So I did the opposite: I worked through a structured, day-by-day Python curriculum, start to finish, in order, no skipping.
The repo is the record of that: a folder per concept, numbered 1 through 20+, starting with variables and string manipulation and ending with instance state, higher-order functions, and turtle-graphics GUIs. Each day builds strictly on the last. That constraint — no jumping ahead, no shortcuts — turned out to be the interesting part.
The shape of the curriculum
The first fifteen days are single-file notebooks, one concept each, with a small project at the end of most of them to force retrieval instead of recognition:
| Days | Concept | Applied project |
|---|---|---|
| 1–5 | Variables, types, control flow, randomization, lists | — |
| 6–8 | Functions, parameters | Hangman, Caesar Cipher |
| 9–11 | Dictionaries, nesting, return values | Secret Auction, Blackjack |
| 12–14 | Scope, debugging | Number Guessing Game, Higher/Lower |
| 15+ | Local environment, OOP, GUIs | Coffee Machine, Quiz Engine, Turtle art |
Day 15 is the hinge point: local environment setup. Everything before it ran in a notebook cell; everything after it is a real multi-file Python package with a main.py entry point, imports across modules, and actual separation of concerns. That's a deliberate pedagogical choice, and it maps to something true about how people actually learn to write software — you don't reach for modules and classes until a script gets big enough that not having them hurts.
Where OOP stopped being abstract
The Coffee Machine project (Day 16) is a small state machine, but it's a correct one — the kind of thing that's easy to get subtly wrong on a first pass:
class CoffeeMaker:
def is_resource_sufficient(self, drink):
can_make = True
for item in drink.ingredients:
if drink.ingredients[item] > self.resources[item]:
print(f"Sorry there is not enough {item}.")
can_make = False
return can_make
def make_coffee(self, order):
for item in order.ingredients:
self.resources[item] -= order.ingredients[item]
print(f"Here is your {order.name} ☕️. Enjoy!")
Three classes — CoffeeMaker, Menu, MoneyMachine — each own their own state, and main.py is reduced to a thin orchestration loop that never reaches into another object's internals directly. That's the actual payoff of OOP that's hard to appreciate from a textbook definition: it's not about syntax, it's about which object is allowed to know what. is_resource_sufficient checks before make_coffee commits — a resource-reservation pattern that shows up constantly in real systems (inventory checks before an order confirms, balance checks before a transaction posts).
The Quiz project one day later applies the same idea with a cleaner three-way split:
quiz = QuizBrain(questionBank)
while quiz.stillHasQuestion():
quiz.nextQuestion()
print(f"Your final score is: {quiz.score}/{quiz.questionNumber}")
Question is a pure data model, QuizBrain owns iteration and scoring state, and main.py never touches QuizBrain's internals — it only calls stillHasQuestion() and nextQuestion(). That's encapsulation doing real work: the calling code can't get the score-tracking logic wrong because it doesn't have access to do so.
Turtle graphics as a forcing function for higher-order thinking
The GUI unit uses Python's turtle module, which looks like a toy but is a genuinely good teaching tool for state and composition because everything is visible immediately — a logic bug in a spirograph isn't an assertion failure, it's a shape that's obviously wrong on screen.
def drawSpirograph(gapSize):
for _ in range(int(360 / gapSize)):
timmyTurtle.color(randomColor())
timmyTurtle.circle(100)
timmyTurtle.setheading(timmyTurtle.heading() + gapSize)
drawSpirograph(5)
This is a higher-order-adjacent pattern — a function parameterized by behavior (gapSize controls both loop count and heading delta) rather than by a fixed sequence of steps. The turtle-race simulation later in the same unit reuses the same idea at a slightly bigger scale: six Turtle instances tracked in a list, each advanced by an independent random distance per tick, with the win condition checked inside the same loop that mutates state:
while isRaceOn:
for turtle in turtles:
if turtle.xcor() > 230:
isRaceOn = False
winningColor = turtle.pencolor()
randomDistance = random.randint(0, 10)
turtle.forward(randomDistance)
It's a simple loop, but it's the same shape as an event loop checking exit conditions on each tick — the kind of pattern that recurs from game loops to polling systems to simulation code.
What's genuinely worth noting about doing it this way
A curriculum like this has an obvious downside: none of these projects are production software, and I wouldn't ship any of this code as-is. But the honest engineering value isn't in the artifacts — it's in what running the full sequence, in order, without skipping, actually forces:
- Retrieval over recognition. Each day's project uses only concepts introduced up to that point, so you can't lean on a library or pattern you haven't earned yet.
- Debugging as its own skill. Day 13 is dedicated entirely to finding and fixing errors — not new syntax, just reading tracebacks and reasoning about state, which is arguably the highest-leverage skill in the whole set.
- The refactor point is visible. You can watch, day by day, exactly where flat scripts stop being adequate and where classes and modules start paying for themselves. That's a judgment call senior engineers make instinctively; working through it explicitly makes the instinct legible.
Takeaway
Fluency in a language isn't the same as having shipped things in it, and I don't conflate the two. What this repo demonstrates is something narrower and, I think, still useful to a client evaluating my work: I don't skip the fundamentals to get to the interesting part faster. Correct resource-checking before a state mutation, encapsulation that actually restricts what calling code can touch, functions parameterized by behavior instead of hardcoded steps — none of that showed up by accident in a Coffee Machine or a Quiz engine. It showed up because I built the habit of writing it correctly at small scale, on purpose, before it mattered at large scale.