InterviewKata
Design patterns · 01 of 26 · Making objects

Factory method

A factory method moves the question "which class do I create?" out of the code that uses the object and into a method that subclasses override. This page builds Pedal's docks that way, adds scooters without editing a line of the old code, and compares it with the simple factory most people use day to day.

1. The pain

Pedal started with one kind of vehicle. Then came e-bikes, which need a battery reading from the dock's charger, and next spring the city wants scooters. The code that releases a vehicle, the code that moves vehicles between stations and the code that books repairs each grew the same if kind == … chain, because each of them needs to create the right object.

Every new kind now means finding and editing every one of those chains, and forgetting one is a bug that only shows up when a scooter reaches that path. The creation decision is copied around, when it should live in one place.

2. The idea in one picture

Every dock in the city releases a vehicle the same way: check the rider, unlock the clamp, start the ride. What rolls out depends on the dock. A classic dock holds pedal bikes; a charging dock holds e-bikes and knows each one's battery level. The procedure is shared; the choice of vehicle belongs to the dock.

Factory method puts object creation behind a method that subclasses override. The base class runs the fixed steps and calls make_vehicle() where it needs a new object; it never names a concrete class. Each subclass answers that one question in its own way, often using its own data (the charging dock passes its charger reading to the e-bike).

A smaller cousin, the simple factory, is just a function that picks the class from data: "this database row says e-bike, so make an EBike". It isn't one of the 23 book patterns, but it's what most people mean by "a factory", and section 5 shows both side by side.

3. The roles

RoleIts jobIn Pedal
ProductThe interface every created object shares; callers use only this.Vehicle: describe(), pence_per_minute()
Concrete productOne real class behind the interface.Bike, EBike, Scooter
CreatorRuns the shared steps and declares the factory method.Dock with release() and make_vehicle()
Concrete creatorOverrides the factory method to return one kind of product.ClassicDock, ChargingDock, ScooterBay

4. Before and after

The same release step written both ways (Python sketch; the full program in three languages is in section 5).

Without the pattern

def release(dock, vid):
    if dock.kind == "classic":
        v = Bike(vid)
    elif dock.kind == "charging":
        v = EBike(vid, dock.charger)
    else:
        raise ValueError(dock.kind)
    start_ride(v)
    return v

# rebalance() and book_repair()
# repeat the same if/elif chain

With it

class Dock(ABC):
    def release(self, vid):
        v = self.make_vehicle(vid)
        start_ride(v)
        return v

    @abstractmethod
    def make_vehicle(self, vid): ...

class ChargingDock(Dock):
    def make_vehicle(self, vid):
        return EBike(vid, self.charger)
The next changeWithout the patternWith it
Add scootersEdit release, rebalance and book_repair: three chainsAdd ScooterBay; no existing class changes
E-bikes need a battery readingFind every EBike( call and pass the readingOne place: ChargingDock.make_vehicle
Test release() without hardwarePatch the module or fake a dock kind stringA FakeDock subclass returns a test vehicle

The pattern doesn't remove the decision; it moves it to the one class that has the information to make it.

5. The code

Three dock classes override make_vehicle(), so release() never names a vehicle class. Below them, a simple factory turns stored records into vehicles and rejects an unknown kind.

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Callable


# ---- The products: what a dock hands to a rider. Callers only ever see Vehicle. ----
class Vehicle(ABC):
    def __init__(self, vid: str) -> None:
        self.vid = vid

    @abstractmethod
    def describe(self) -> str: ...

    @abstractmethod
    def pence_per_minute(self) -> int: ...


class Bike(Vehicle):
    def describe(self) -> str:
        return f"bike {self.vid} (pedal only)"

    def pence_per_minute(self) -> int:
        return 10


class EBike(Vehicle):
    def __init__(self, vid: str, battery: int) -> None:
        super().__init__(vid)
        self.battery = battery

    def describe(self) -> str:
        return f"e-bike {self.vid} (battery {self.battery}%)"

    def pence_per_minute(self) -> int:
        return 15


class Scooter(Vehicle):
    def __init__(self, vid: str, max_kmh: int) -> None:
        super().__init__(vid)
        self.max_kmh = max_kmh

    def describe(self) -> str:
        return f"scooter {self.vid} (max {self.max_kmh} km/h)"

    def pence_per_minute(self) -> int:
        return 20


# ---- Factory method: release() is the same for every dock; each kind of dock decides what it creates. ----
class Dock(ABC):
    def __init__(self, label: str, name: str) -> None:
        self.label = label
        self.name = name

    def release(self, vid: str) -> str:
        vehicle = self.make_vehicle(vid)          # the factory method: no class named here
        return f"{self.label} {self.name} released {vehicle.describe()} | {vehicle.pence_per_minute()}p a minute"

    @abstractmethod
    def make_vehicle(self, vid: str) -> Vehicle: ...


class ClassicDock(Dock):
    def __init__(self, name: str) -> None:
        super().__init__("classic dock", name)

    def make_vehicle(self, vid: str) -> Vehicle:
        return Bike(vid)


class ChargingDock(Dock):
    def __init__(self, name: str, charger_reading: int) -> None:
        super().__init__("charging dock", name)
        self.charger_reading = charger_reading

    def make_vehicle(self, vid: str) -> Vehicle:
        return EBike(vid, self.charger_reading)   # uses the dock's own state


class ScooterBay(Dock):                           # added later: no existing class was edited
    def __init__(self, name: str, speed_limit: int) -> None:
        super().__init__("scooter bay", name)
        self.speed_limit = speed_limit

    def make_vehicle(self, vid: str) -> Vehicle:
        return Scooter(vid, self.speed_limit)


# ---- A simple factory: one function that turns a stored record into the right object. ----
MAKERS: dict[str, Callable[[str], Vehicle]] = {
    "bike": lambda vid: Bike(vid),
    "e-bike": lambda vid: EBike(vid, 100),
    "scooter": lambda vid: Scooter(vid, 15),
}


def vehicle_from_record(kind: str, vid: str) -> Vehicle:
    if kind not in MAKERS:
        raise ValueError(f"unknown vehicle kind '{kind}'")
    return MAKERS[kind](vid)


if __name__ == "__main__":
    docks: list[Dock] = [ClassicDock("D1"), ChargingDock("D7", 84), ScooterBay("S2", 15)]
    for dock, vid in zip(docks, ["B-101", "E-205", "K-310"]):
        print(dock.release(vid))
    records = [("bike", "B-101"), ("e-bike", "E-205"), ("scooter", "K-310")]
    print("loaded from records: " + ", ".join(vehicle_from_record(k, v).describe() for k, v in records))
    try:
        vehicle_from_record("hoverboard", "H-1")
    except ValueError as e:
        print(f"rejected: {e}")
// ---- The products: what a dock hands to a rider. Callers only ever see Vehicle. ----
abstract class Vehicle {
  constructor(readonly vid: string) {}
  abstract describe(): string;
  abstract pencePerMinute(): number;
}

class Bike extends Vehicle {
  describe(): string { return `bike ${this.vid} (pedal only)`; }
  pencePerMinute(): number { return 10; }
}

class EBike extends Vehicle {
  constructor(vid: string, readonly battery: number) { super(vid); }
  describe(): string { return `e-bike ${this.vid} (battery ${this.battery}%)`; }
  pencePerMinute(): number { return 15; }
}

class Scooter extends Vehicle {
  constructor(vid: string, readonly maxKmh: number) { super(vid); }
  describe(): string { return `scooter ${this.vid} (max ${this.maxKmh} km/h)`; }
  pencePerMinute(): number { return 20; }
}

// ---- Factory method: release() is the same for every dock; each kind of dock decides what it creates. ----
abstract class Dock {
  constructor(readonly label: string, readonly name: string) {}

  release(vid: string): string {
    const vehicle = this.makeVehicle(vid);       // the factory method: no class named here
    return `${this.label} ${this.name} released ${vehicle.describe()} | ${vehicle.pencePerMinute()}p a minute`;
  }

  protected abstract makeVehicle(vid: string): Vehicle;
}

class ClassicDock extends Dock {
  constructor(name: string) { super("classic dock", name); }
  protected makeVehicle(vid: string): Vehicle { return new Bike(vid); }
}

class ChargingDock extends Dock {
  constructor(name: string, private readonly chargerReading: number) { super("charging dock", name); }
  protected makeVehicle(vid: string): Vehicle { return new EBike(vid, this.chargerReading); }   // uses the dock's own state
}

class ScooterBay extends Dock {                   // added later: no existing class was edited
  constructor(name: string, private readonly speedLimit: number) { super("scooter bay", name); }
  protected makeVehicle(vid: string): Vehicle { return new Scooter(vid, this.speedLimit); }
}

// ---- A simple factory: one function that turns a stored record into the right object. ----
const MAKERS: Record<string, (vid: string) => Vehicle> = {
  "bike": (vid) => new Bike(vid),
  "e-bike": (vid) => new EBike(vid, 100),
  "scooter": (vid) => new Scooter(vid, 15),
};

function vehicleFromRecord(kind: string, vid: string): Vehicle {
  const make = MAKERS[kind];
  if (!make) throw new Error(`unknown vehicle kind '${kind}'`);
  return make(vid);
}

const docks: Dock[] = [new ClassicDock("D1"), new ChargingDock("D7", 84), new ScooterBay("S2", 15)];
const vids = ["B-101", "E-205", "K-310"];
docks.forEach((dock, i) => console.log(dock.release(vids[i])));
const records: [string, string][] = [["bike", "B-101"], ["e-bike", "E-205"], ["scooter", "K-310"]];
console.log("loaded from records: " + records.map(([k, v]) => vehicleFromRecord(k, v).describe()).join(", "));
try {
  vehicleFromRecord("hoverboard", "H-1");
} catch (e) {
  console.log(`rejected: ${(e as Error).message}`);
}
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class Main {
    // ---- The products: what a dock hands to a rider. Callers only ever see Vehicle. ----
    abstract static class Vehicle {
        final String vid;
        Vehicle(String vid) { this.vid = vid; }
        abstract String describe();
        abstract int pencePerMinute();
    }

    static class Bike extends Vehicle {
        Bike(String vid) { super(vid); }
        String describe() { return "bike " + vid + " (pedal only)"; }
        int pencePerMinute() { return 10; }
    }

    static class EBike extends Vehicle {
        final int battery;
        EBike(String vid, int battery) { super(vid); this.battery = battery; }
        String describe() { return "e-bike " + vid + " (battery " + battery + "%)"; }
        int pencePerMinute() { return 15; }
    }

    static class Scooter extends Vehicle {
        final int maxKmh;
        Scooter(String vid, int maxKmh) { super(vid); this.maxKmh = maxKmh; }
        String describe() { return "scooter " + vid + " (max " + maxKmh + " km/h)"; }
        int pencePerMinute() { return 20; }
    }

    // ---- Factory method: release() is the same for every dock; each kind of dock decides what it creates. ----
    abstract static class Dock {
        final String label, name;
        Dock(String label, String name) { this.label = label; this.name = name; }

        String release(String vid) {
            Vehicle vehicle = makeVehicle(vid);   // the factory method: no class named here
            return label + " " + name + " released " + vehicle.describe() + " | " + vehicle.pencePerMinute() + "p a minute";
        }

        protected abstract Vehicle makeVehicle(String vid);
    }

    static class ClassicDock extends Dock {
        ClassicDock(String name) { super("classic dock", name); }
        protected Vehicle makeVehicle(String vid) { return new Bike(vid); }
    }

    static class ChargingDock extends Dock {
        private final int chargerReading;
        ChargingDock(String name, int chargerReading) { super("charging dock", name); this.chargerReading = chargerReading; }
        protected Vehicle makeVehicle(String vid) { return new EBike(vid, chargerReading); }   // uses the dock's own state
    }

    static class ScooterBay extends Dock {        // added later: no existing class was edited
        private final int speedLimit;
        ScooterBay(String name, int speedLimit) { super("scooter bay", name); this.speedLimit = speedLimit; }
        protected Vehicle makeVehicle(String vid) { return new Scooter(vid, speedLimit); }
    }

    // ---- A simple factory: one function that turns a stored record into the right object. ----
    static final Map<String, Function<String, Vehicle>> MAKERS = Map.of(
        "bike", Bike::new,
        "e-bike", vid -> new EBike(vid, 100),
        "scooter", vid -> new Scooter(vid, 15));

    static Vehicle vehicleFromRecord(String kind, String vid) {
        Function<String, Vehicle> make = MAKERS.get(kind);
        if (make == null) throw new IllegalArgumentException("unknown vehicle kind '" + kind + "'");
        return make.apply(vid);
    }

    public static void main(String[] args) {
        List<Dock> docks = List.of(new ClassicDock("D1"), new ChargingDock("D7", 84), new ScooterBay("S2", 15));
        String[] vids = {"B-101", "E-205", "K-310"};
        for (int i = 0; i < docks.size(); i++) System.out.println(docks.get(i).release(vids[i]));
        String[][] records = {{"bike", "B-101"}, {"e-bike", "E-205"}, {"scooter", "K-310"}};
        List<String> loaded = new ArrayList<>();
        for (String[] r : records) loaded.add(vehicleFromRecord(r[0], r[1]).describe());
        System.out.println("loaded from records: " + String.join(", ", loaded));
        try {
            vehicleFromRecord("hoverboard", "H-1");
        } catch (IllegalArgumentException e) {
            System.out.println("rejected: " + e.getMessage());
        }
    }
}

Output (the same in all three languages)

classic dock D1 released bike B-101 (pedal only) | 10p a minute
charging dock D7 released e-bike E-205 (battery 84%) | 15p a minute
scooter bay S2 released scooter K-310 (max 15 km/h) | 20p a minute
loaded from records: bike B-101 (pedal only), e-bike E-205 (battery 100%), scooter K-310 (max 15 km/h)
rejected: unknown vehicle kind 'hoverboard'

6. Try it

Add a vehicle kind

Pedal keeps adding vehicles. Add them one at a time and watch what each design asks you to edit.

Without the pattern: release()

With factory method: dock classes

Simplified: three functions create vehicles in the "without" design (release, rebalance, book repair); each needs one more branch per kind.

7. When not to use it

What you get

  • New kinds without edits: add a creator subclass; the shared steps stay closed.
  • Creation in one place: the class that has the data makes the object.
  • Callers depend on the interface: release() only knows Vehicle.
  • Easy fakes: a test subclass returns whatever the test needs.

What you pay

  • One more class per kind: creators grow alongside products.
  • Indirection: a reader must find which subclass ran.
  • Inheritance for a small choice: subclassing just to pick a class is heavy when nothing else varies.
  • Upfront cost: wasted if a second kind never comes.
If…Use instead
There is one kind, and no second is plannedCall the constructor
The choice depends on data, not on who createsA simple factory: a function or a dict of makers (section 5)
You need a matching set of objects per city02 Abstract factory
Creation has many optional settings03 Builder
The creator does nothing else but createPass a maker function in: 24 Dependency injection

8. How it goes wrong

A factory for a single classWith one product and no second on the way, the factory is ceremony. Call the constructor and refactor when the second kind arrives.
Returning the concrete classIf make_vehicle() is typed to return EBike, callers start using e-bike-only methods and the decoupling is gone. Return the interface.
Moving the if/elif, not removing itA base-class factory method that still switches on a kind string is a simple factory in disguise; every new kind edits it again.
A creator subclass for every productIf the creators differ only in which class they construct, a dict of maker functions is shorter and does the same job.

9. Remember it and say it

Memory hook: "Same door, different vehicle"The release steps are fixed; each dock decides what rolls out.
How this hook works: what “Same door, different vehicle” means

What the slogan means, phrase by phrase

“Same door”
The base class's fixed steps (release()).
“different vehicle”
The product each subclass creates.
“each dock decides”
The overridden factory method.

Why this picture fits: Riders never ask for a class; they walk up to a dock and get what it holds.

The story → the concept

The dockThe code
The release procedureDock.release()
What the dock holdsmake_vehicle() in each subclass
A new scooter bayA new subclass; old docks untouched
The riderCode that only knows Vehicle

Watch it on an example

  1. The rider unlocks at charging dock D7.
  2. release() runs the shared steps and calls make_vehicle().
  3. ChargingDock creates an e-bike with its charger reading; release() sees only a Vehicle.

The trap it saves you from

Copying the same if kind == chain into every function that creates objects.

It still works when…

Yes, even without inheritance: pass the creating function in, and the same idea holds.

In an interview or code review

10. Check yourself

What does a factory method let a subclass decide?

The shared steps stay in the base class; only creation varies.

Pedal adds scooters. With factory method, what changes?

The base class never named a concrete class, so it doesn't change.

A database row says kind = "e-bike". What fits best?

The choice comes from data, not from who is creating.

Why should make_vehicle() return Vehicle rather than EBike?

Returning the interface keeps the decoupling the pattern exists for.

When is a factory method overkill?

Add it when the second kind arrives.

11. Go deeper

ResourceWhy
Low-level design 04: Patterns you'll actually useThe short interview tour; this track goes deeper on each.
Low-level design 03b: Open-closed principleThe principle this pattern serves: add code, don't edit it.
Java Collection.iterator()A factory method in the standard library: each collection makes its own iterator.
Wikipedia: Factory method patternHistory, variants and language-by-language notes.