Flex Engineers
5 min read

100 Days of Python: What a Structured Company Curriculum Actually Teaches

PythonOOPSoftware DevelopmentTechnical MentoringBackend Development

Why we run engineers through 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 as part of our growth track, we ask engineers to do the opposite: work through a structured, day-by-day Python curriculum, start to finish, in order, no skipping.

The repo one of our engineers produced 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, and it's exactly the discipline we look for before staffing someone on a Python backend engagement.

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:

DaysConceptApplied project
1–5Variables, types, control flow, randomization, lists—
6–8Functions, parametersHangman, Caesar Cipher
9–11Dictionaries, nesting, return valuesSecret Auction, Blackjack
12–14Scope, debuggingNumber Guessing Game, Higher/Lower
15+Local environment, OOP, GUIsCoffee 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), and it's a pattern our engineers now reach for by instinct on client backend work.

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 our team writes for clients.

What's genuinely worth noting about running the program this way

A curriculum like this has an obvious downside: none of these projects are production software, and we wouldn't ship any of this code as-is to a client. But the honest engineering value isn't in the artifacts — it's in what running the full sequence, in order, without skipping, actually forces on an engineer:

  • Retrieval over recognition. Each day's project uses only concepts introduced up to that point, so an engineer can't lean on a library or pattern they 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 we can teach.
  • The refactor point is visible. An engineer 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, and legible instincts are what we can actually mentor around.

Takeaway

Fluency in a language isn't the same as having shipped things in it, and we don't conflate the two when we evaluate an engineer's growth. What this program demonstrates is something narrower and still useful to a client evaluating our team's work: our engineers 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 shows up by accident in a Coffee Machine or a Quiz engine. It shows up because we build the habit of writing it correctly at small scale, on purpose, before it matters at large scale on a client's production system.