Course contents

Factory pattern — hiding object construction

Sign in to run this lab and save your progress

Learn

Before you start

Welcome to design patterns. If you take one thing from this module, take this:

Knowing patterns is not the interview skill. Choosing the right one for a specific problem is.

Almost every candidate can list Factory / Strategy / Observer and describe what they do. Almost none can look at a new problem, ask three good questions, and say "Factory here because of X, not Strategy because of Y". That's the gap this module drills.

A checklist before you use any pattern

Walk this checklist BEFORE reaching for a pattern:

  1. What does the problem actually need? List what varies. (In our parking lot: the specific class varies based on an input code.)
  2. What's the simplest thing that could work? Start there. Only make it fancier if the simple thing hurts.
  3. What are 2-3 different designs? For each, know the good parts, the bad parts, and the exact requirement that makes it win or lose.
  4. Which requirement makes the choice? Say it out loud: "I'm picking X because the requirements say Y."

Skip step 4 and the interviewer will ask "why not Z?" and you won't have an answer. That's not a design failure — that's a communication failure. The choice was in your head; you didn't SHOW them how you got there.

When to use a factory (any flavor)

The factory family answers one question: how do we hide the "which class do I build?" decision from the caller?

Signs you need one:

  • The class you build depends on data at runtime. The caller has a string / config / DB row, not a class name.
  • Callers shouldn't import specific classes. They should hold a Vehicle, not a Car / Truck / Motorcycle.
  • New types will be added later. Callers shouldn't have to change when new types show up.
  • Building the object has cross-cutting logic (logging, metrics, auth check) that shouldn't be copied everywhere.

Any of these → some kind of factory. Which specific flavor depends on the answers to the questions in the objective.

The factory flavors, at a glance

Flavor Best when Not when
Inline if/elif 1 place builds it, 2-3 types forever Anything else
Dict + plain function All types take the same args, few callers Args differ per type
Simple Factory class (this lesson) Args differ per type, want one place for cross-cutting logic, list will grow Only ever 1 type
GoF Factory Method (child overrides builder) Parallel class trees where each child owns a whole workflow AND builds a type-specific object Simple "just build the right thing" — this is overkill
Abstract Factory (next lesson) Need to build FAMILIES of related objects together (like a whole UI theme) Only one thing to build

For interviews, the top three rows cover 95% of "give me a factory" questions.

Where you'll see Factory later in this course

  • Parking Lot — VehicleFactory (this lesson's task)
  • Vending Machine — ProductFactory (Snack / Beverage / ColdItem based on the slot type)
  • Chess — PieceFactory from FEN notation ("Q" → Queen, "k" → BlackKing)
  • Notification — NotifierFactory based on user preferences
  • File System — NodeFactory that returns File or Directory based on what's on disk

Every problem where "the type comes from data" → factory.

Common mistakes to avoid

  1. Building a factory for one type. If there's only one class, just call Car(plate). A factory for one type is extra work without payoff.

  2. Putting if/elif inside the factory. If your create() contains if type_code == "CAR": return Car(...), you've just moved the mess into a new building. The whole POINT of the registry is to remove the if/elif. The test on this lesson catches this.

  3. Confusing "Simple Factory" with GoF "Factory Method". Simple Factory is a class or function that returns the right instance. GoF Factory Method is a parent-class hook that children override to build a type-specific object as part of a shared workflow. Different patterns. Different when-to-use. Interviewers who know the GoF book will notice if you mix them up.

What the tests in this lesson check

Three things:

  • Correctness — the factory returns the right subclasses.
  • Extensibility — a new type registered from outside the factory must work (the OCP payoff).
  • Anti-smell — the AST check that if/elif never sneaks back in.

That third one is where design quality lives. Pass all three and you have a factory design an interviewer would sign off on.

Your task

Welcome to the design patterns module. This module teaches you to spot which pattern a problem needs, not just how to write the patterns in isolation.

Every lesson has the same shape:

  1. A real problem
  2. Questions to ask about the requirements
  3. A few different designs, compared to each other
  4. A locked-in choice — with the exact requirement that ruled out each other option
  5. The code
  6. Your task

The choice is the interview skill. Anyone can watch a YouTube video and describe the Factory pattern. The real interview question is: "why THIS pattern for THIS problem, and why not those other three?"


The problem

You're designing a parking lot. Vehicles arrive at the entry scanner, which reads a plate and emits a type code ("CAR", "BIKE", "TRUCK", "EV"). Your system needs to construct the right Vehicle subclass from that code so downstream code (spot assignment, fee calculation) has the right object.

Naive first attempt:

def handle_entry(type_code, plate):
    if type_code == "CAR":
        v = Car(plate)
    elif type_code == "BIKE":
        v = Motorcycle(plate)
    elif type_code == "TRUCK":
        v = Truck(plate)
    elif type_code == "EV":
        v = ElectricCar(plate)
    else:
        raise ValueError(f"unknown vehicle type {type_code}")
    spots.assign(v)
    fees.start_meter(v)

Feels off. Let's figure out why, and what to replace it with.

Questions to ask about the requirements

Before writing any code, an interviewer expects you to ask questions. For this problem the questions that matter:

  1. How many vehicle types will exist? Fixed at three, or growing?
  2. Can new vehicle types be added while the app is running (through config or plugins), or is the set locked when you deploy?
  3. Does the caller ever need to know the specific type, or only that it's a Vehicle?
  4. How many places in the codebase build vehicles from a type code? One (entry scanner) or many (entry + admin + tests + reports)?
  5. Are there rules other than "type code → class" for picking which class to build? For example: an EV that's a truck becomes ElectricTruck.
  6. Do child classes need different constructor arguments? (An ElectricCar needs battery capacity; a plain Car doesn't.)

Different answers point to different patterns. That's the whole game.

Different designs, compared

Four designs are common here. Understand each.

Design A — inline if/elif at every place that builds a vehicle

The naive version above.

  • Good: Simplest possible code. No extra layers. Fine when the set is small AND only one place builds vehicles AND it'll never grow.
  • Bad: Breaks OCP (module 2, lesson 2). Every new type means editing every place. As soon as vehicles are built in 2+ places, you're copying the dispatch.
  • When it wins: One place, 2-3 types, never grows. Rare.

Design B — a plain function using a dict

_REGISTRY = {"CAR": Car, "BIKE": Motorcycle, "TRUCK": Truck}

def make_vehicle(type_code, plate):
    cls = _REGISTRY.get(type_code)
    if cls is None:
        raise ValueError(f"unknown vehicle type {type_code}")
    return cls(plate)
  • Good: Very Pythonic. Adding a type is one line in the dict. Callers get a clean make_vehicle(code, plate).
  • Bad: Every child class must take the SAME constructor args (plate). Breaks if ElectricCar needs battery capacity and Truck needs cargo capacity. Also no place to add cross-cutting logic (logging every creation, validating the type code).
  • When it wins: All child classes take the same arguments, and the codebase is small. Frankly, this is the right answer for many Python problems.

Design C — GoF Factory Method (parent class + children override the builder)

class ParkingLotEntry(ABC):
    @abstractmethod
    def create_vehicle(self, plate): ...

    def handle_entry(self, plate):
        v = self.create_vehicle(plate)
        # ... rest of entry flow

class CarEntry(ParkingLotEntry):
    def create_vehicle(self, plate): return Car(plate)

class TruckEntry(ParkingLotEntry):
    def create_vehicle(self, plate): return Truck(plate)
  • Good: Each child class comes with its whole workflow. Useful when the ENTIRE process around building the object differs per type (a truck entry validates differently from a car entry).
  • Bad: Overkill for our problem. We don't have different workflows per type — just different classes to build. This version of Factory Method creates parallel class trees you don't need.
  • When it wins: When you already have a class tree where each child owns a whole workflow AND part of that workflow is "build the type-specific object". Common in framework code (like Django's ModelAdmin creating a form). Rare in plain design problems.

Design D — Simple Factory (class with a create classmethod + internal registry)

class VehicleFactory:
    _registry: dict = {}

    @classmethod
    def register(cls, type_code, vehicle_cls):
        cls._registry[type_code] = vehicle_cls

    @classmethod
    def create(cls, type_code, **kwargs):
        vehicle_cls = cls._registry.get(type_code)
        if vehicle_cls is None:
            raise ValueError(f"unknown vehicle type {type_code}")
        return vehicle_cls(**kwargs)

VehicleFactory.register("CAR", Car)
VehicleFactory.register("BIKE", Motorcycle)
VehicleFactory.register("TRUCK", Truck)
  • Good: Handles different constructor arguments per type via **kwargs. Extensible without editing the factory itself (you can register from another module). One place for cross-cutting logic (logging, validation). Feels natural in an object-oriented design.
  • Bad: More layers than Design B. **kwargs is looser than named parameters. Overkill if you'll never have more than 3-4 types.
  • When it wins: Different constructor arguments per type, multiple places that build vehicles, the list is likely to grow, and you want somewhere to add cross-cutting logic. This is the sweet spot for the parking lot problem.

The locked-in choice for THIS problem: Design D

Our assumed answers:

  • Types will grow (EV, then LargeEV, then AutonomousShuttle...). → Rules out A (every new type means editing every place).
  • Child classes need different constructor args (EV needs battery, Truck needs cargo capacity). → Rules out B (a flat cls(plate) can't handle different args cleanly).
  • The entry flow is the same for every vehicle — read plate, build vehicle, assign spot. No per-type workflow. → Rules out C (no reason to build parallel class trees).
  • We want ONE place that knows "type code → class". Multiple callers (entry scanner, admin form, tests). → Selects D — the class-based factory with a registry.

Notice how the pattern falls out of the requirements. If the answers were different (3 types forever, all taking the same args), Design B would win instead. There's no universal winner.

Mechanics for Design D

class Vehicle(ABC):
    @abstractmethod
    def describe(self): ...

class Car(Vehicle):
    def __init__(self, plate):
        self.plate = plate
    def describe(self):
        return f"car {self.plate}"

class Truck(Vehicle):
    def __init__(self, plate, cargo_capacity_kg):
        self.plate = plate
        self.cargo_capacity_kg = cargo_capacity_kg
    def describe(self):
        return f"truck {self.plate} ({self.cargo_capacity_kg}kg)"

class VehicleFactory:
    _registry = {}

    @classmethod
    def register(cls, type_code, vehicle_cls):
        cls._registry[type_code] = vehicle_cls

    @classmethod
    def create(cls, type_code, **kwargs):
        vehicle_cls = cls._registry.get(type_code)
        if vehicle_cls is None:
            raise ValueError(f"unknown vehicle type {type_code!r}")
        return vehicle_cls(**kwargs)

VehicleFactory.register("CAR", Car)
VehicleFactory.register("TRUCK", Truck)

car = VehicleFactory.create("CAR", plate="KA-01-1234")
truck = VehicleFactory.create(
    "TRUCK", plate="KA-01-9999", cargo_capacity_kg=5000
)

Your task

Build the parking lot vehicle factory in main.py:

  1. Abstract Vehicle(ABC) with abstract describe(self).
  2. Three concrete subclasses:
    • Car(plate)describe() returns "car <plate>"
    • Motorcycle(plate)"motorcycle <plate>"
    • Truck(plate, cargo_capacity_kg)"truck <plate> (<cargo_capacity_kg>kg)"
  3. VehicleFactory class with:
    • Class-level _registry dict (starts empty)
    • register(type_code, vehicle_cls) classmethod
    • create(type_code, **kwargs) classmethod that looks up the class, constructs with **kwargs, and raises ValueError for unknown codes
  4. At module import, register the three built-in types with codes "CAR", "BIKE", "TRUCK".

Constraints (structural tests enforce):

  • VehicleFactory.create must NOT contain if/elif chains on the type code — the dispatch is via the registry only. The smell we started with must not sneak back in.
  • Adding a new type must be possible via VehicleFactory.register(...) alone, with zero changes to existing factory code.

Click Submit to run the tests.

Starter files

Preview only — open the lab to edit and run.

files_dir
01-factory-method-starter