
The factory pattern puts the job of making objects in one place. Instead of every part of your code deciding which class to build, it asks one function, the factory, by name: "give me the SMS notifier". The factory knows which class that is and how to set it up, and hands the finished object back.
In this post we start at a restaurant counter. Then we look at what goes wrong when the same if chain that picks a class is copied all over an app, and fix it with a small factory in plain Python: a dictionary from names to classes. We hand that factory to a service the way the last post handed in a mailer, test it with a fake, see where Python itself uses factories, and finish with how a factory differs from the strategy pattern and when a factory is more than you need.
A few words first. A class is like a recipe, and an object is one thing made from it. A constructor is the code that runs when an object is made; in Python it's __init__. A notifier here is an object that sends a message to a user, by email or by SMS. A dictionary (a dict) is Python's lookup table: you give it a key, such as "sms", and get back the value stored under it.
Key takeaways
- A factory is a function (or object) whose only job is to make other objects. You ask for one by name or by setting; it picks the class and builds it.
- It earns its place when the same choice is made in many places, or when each class needs different setup. The choice then lives in one file, and adding a new kind is one new class plus one line on the menu.
- In Python, the simplest factory is a dictionary from names to classes, plus one function that looks the name up.
functools.partialstores each class's settings. - A good factory fails loudly on a name it doesn't know. A silent default turns a typo like
"SMS"into the wrong object and nobody notices. - Factories and dependency injection fit together: hand the factory (or what it built) into the class that needs it, and pass a fake one in tests.
- Don't wrap a single class in a factory, and don't move an
ifchain that's used once into its own file. That's more code for no gain.
What is the factory pattern in simple words?
Think of a restaurant counter. You say "one masala dosa". You don't walk into the kitchen, pick a cook, find the batter and heat the pan. The counter turns the name of a dish into the dish. If the restaurant adds a new dish, it adds one line to the menu, and nothing changes for you: you still just say a name. And if you ask for something that isn't on the menu, the counter tells you so. It doesn't hand you a random plate.
Code is the same. Our factory is the counter. The menu is a dictionary from names to classes. You call make_notifier("sms") and get back a ready-to-use SMS notifier, set up with the right sender name, without knowing how it was built. Ask for "fax" and you get a clear error.
Python is friendly to this idea because a class can already be called like a function. The Python tutorial says: "Class instantiation uses function notation. Just pretend that the class object is a parameterless function that returns a new instance of the class." Instantiation means making an object from a class, and an instance is one object made that way. So EmailNotifier is already a tiny factory for email notifiers. The factory pattern adds the step before: choosing which class to call.
What problem does the factory pattern solve?
Here is a small shop app. When an order ships, the user gets a message, by the channel they picked in their settings: email, SMS, or "off" for no messages. First, the notifiers. A real app would call an email service or an SMS gateway (a company that delivers text messages); ours print, so the example runs anywhere:
# notifiers.py
class EmailNotifier:
def __init__(self, sender):
self.sender = sender
def send(self, to, message):
print(f"Email from {self.sender} to {to}: {message}")
class SmsNotifier:
def __init__(self, sender_id):
self.sender_id = sender_id
def send(self, to, message):
print(f"SMS from {self.sender_id} to {to}: {message}")
class NullNotifier:
"""For users who turned messages off: sends nothing."""
def send(self, to, message):
pass
All three have the same send(to, message) method, so any code that has a notifier can call send without caring which one it is. But they're built differently: the email one needs a sender address, the SMS one needs a sender ID (the short name a phone shows as who sent the text), and the null one needs nothing.
Now the order service, written the way most of us write it first, with the choice made right where it's needed:
# orders_before.py
from notifiers import EmailNotifier, NullNotifier, SmsNotifier
class OrderService:
def ship(self, user, order_id):
if user["channel"] == "email":
notifier = EmailNotifier(sender="orders@letsbug.in")
elif user["channel"] == "sms":
notifier = SmsNotifier(sender_id="LTSBUG")
else: # "off"
notifier = NullNotifier()
notifier.send(user["contact"], f"Order {order_id} has shipped")
It works. Then the app grows, and three things go wrong:
- The chain gets copied. The password-reset code needs to message users too, and so does the one-time-password code. Each gets its own copy of these six lines. Now there are three places that know the SMS sender ID.
- Adding a channel means editing every copy. When WhatsApp comes along, you have to find and change every chain, and the one you miss quietly does the wrong thing.
- Typos vanish. If a user's setting says
"SMS"or"e-mail", it matches noif, falls into theelse, and that user silently gets no messages at all. No error, nothing in the logs.
The service has also mixed two jobs: shipping orders, and knowing how to build every kind of notifier. The factory pulls the second job out.
How does the factory pattern work?
Move the choice into one function. Everything that needs a notifier calls it with a name and gets an object back. That's it: one place that knows the names, the classes and their settings, and a clear error for any name it doesn't know.

Martin Fowler, the software author whose 2004 article gave dependency injection its name, makes a related point about building objects. His "Factory Methods" are named functions that build one class in a particular way: "If you have multiple ways to construct a valid object, it can be hard to show this through constructors, since constructors can only vary on the number and type of parameters. This is when Factory Methods come into play". A constructor can only show one way to build one class. Our case is bigger: three classes, each with its own settings, and the factory is the one place that knows all three. What we're building is often called a simple factory: one function that picks a class and builds it.
How do you write a factory in Python?
In Python, the menu is a dictionary. The Python FAQ suggests a dictionary for choosing between many options, and it works just as well for three: "For cases where you need to choose from a very large number of possibilities, you can create a dictionary mapping case values to functions to call." Classes can be called like functions, so they can go straight into the dictionary. Here's our factory, standard library only (the tools that come with Python), so there's nothing to install:
# factory.py
from functools import partial
from notifiers import EmailNotifier, NullNotifier, SmsNotifier
BUILDERS = {
"email": partial(EmailNotifier, sender="orders@letsbug.in"),
"sms": partial(SmsNotifier, sender_id="LTSBUG"),
"off": NullNotifier,
}
def make_notifier(channel):
"""Build the notifier for a channel name, such as "email" or "sms"."""
if channel not in BUILDERS:
raise ValueError(f"unknown channel {channel!r}, expected one of {sorted(BUILDERS)}")
return BUILDERS[channel]()
BUILDERSis the menu. Each name maps to something that, when called with no arguments, builds that notifier.partialcomes from thefunctoolsmodule. The docs: it returns "a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords." In plain words,partial(EmailNotifier, sender="orders@letsbug.in")is "EmailNotifierwith the sender already filled in". Calling it later gives a ready email notifier.NullNotifierneeds no settings, so the class itself goes in, with nopartial.- An unknown name raises a
ValueErrorthat says what was wrong and lists the names that are allowed.{channel!r}prints the name with quotes around it, so a stray space at the start or end shows up.
Python 3.10 added another way to choose. The same FAQ: "Since Python 3.10 one can easily match literal values, or constants within a namespace, with a match ... case statement." A match inside make_notifier works too. We use the dictionary because the list of allowed names comes for free, and the error message uses it.
The "off" channel is worth a second look. We could have returned None for it, but then every caller would need an if notifier is not None check before sending. Instead, the factory returns a notifier that does nothing. Fowler calls this a Special Case: "Instead of returning null, or some odd value, return a Special Case that has the same interface as what the caller expects." The interface is the set of methods the caller expects, here just send, and null is other languages' word for Python's None. Fowler describes a Special Case as a subclass; ours isn't one, because in Python it only needs the same send method.
How do a factory and dependency injection work together?
In the last post, the sign-up service didn't build its own mailer: main.py built one and handed it in. Something still had to choose which mailer to build, and that's often a factory's job. There are two ways to combine them:
- The choice is made once, from a setting such as an environment variable (a named setting the operating system passes to your program).
main.pycalls the factory once at start-up and injects the object it gets back. The class never sees the factory. In the repository pattern post,main.pypicked the SQLite repository by hand; amake_repository("sqlite")ormake_repository("memory")called once at start-up would be this case. - The choice is made again and again, as here, where each user has their own channel. Then you inject the factory itself, and the class calls it when it needs a notifier.
Fowler notes that a factory doesn't remove the wiring, it moves it: "A factory service is often a good tactic, but you still have to instantiate the factory using one of the techniques here." By "the techniques here" he means ways like dependency injection: the factory itself still has to be handed to whoever uses it. Here's the order service with the factory handed in, through the constructor like last time:
# orders.py
class OrderService:
def __init__(self, make_notifier):
self.make_notifier = make_notifier # the factory is handed in
def ship(self, user, order_id):
notifier = self.make_notifier(user["channel"])
notifier.send(user["contact"], f"Order {order_id} has shipped")
The if chain is gone, and orders.py doesn't import a single notifier. main.py is the one place that wires it up:
# main.py
from factory import make_notifier
from orders import OrderService
users = [
{"name": "Asha", "channel": "email", "contact": "asha@example.com"},
{"name": "Ravi", "channel": "sms", "contact": "+91 90000 00001"},
{"name": "Meera", "channel": "off", "contact": "meera@example.com"},
]
service = OrderService(make_notifier)
for order_id, user in enumerate(users, start=101):
service.ship(user, order_id)
enumerate(users, start=101) walks through the list and counts as it goes, so the orders get numbers 101, 102 and 103. Put the four files in one folder and run python main.py:
Email from orders@letsbug.in to asha@example.com: Order 101 has shipped
SMS from LTSBUG to +91 90000 00001: Order 102 has shipped
Two lines, not three: Meera turned messages off, so her NullNotifier sent nothing, and OrderService didn't need an if to make that happen.
Now the tests. Because the factory is handed in, a test can hand in a fake factory that always returns a fake notifier: one that sends nothing and writes down what it was asked to send. We also test the real factory on its own.
# test_orders.py
import unittest
from factory import make_notifier
from notifiers import EmailNotifier, NullNotifier, SmsNotifier
from orders import OrderService
class FakeNotifier:
"""Sends nothing. Writes down every message it was asked to send."""
def __init__(self):
self.sent = []
def send(self, to, message):
self.sent.append((to, message))
class FactoryTest(unittest.TestCase):
def test_each_channel_gets_its_class(self):
self.assertIsInstance(make_notifier("email"), EmailNotifier)
self.assertIsInstance(make_notifier("sms"), SmsNotifier)
self.assertIsInstance(make_notifier("off"), NullNotifier)
def test_unknown_channel_fails_loudly(self):
with self.assertRaises(ValueError):
make_notifier("SMS")
class OrderTest(unittest.TestCase):
def test_ship_sends_one_message(self):
fake = FakeNotifier()
service = OrderService(lambda channel: fake)
service.ship({"channel": "sms", "contact": "+91 90000 00001"}, 7)
self.assertEqual(fake.sent, [("+91 90000 00001", "Order 7 has shipped")])
assertIsInstancechecks that an object was made from a given class: ask for"sms", get anSmsNotifier.assertRaises(ValueError)checks that the typo"SMS"is refused, not quietly turned into "off".lambda channel: fakeis a tiny one-line function: it takes a channel name and always returns our fake. That's a fake factory, and it fits where the real one went, because both take a name and return something withsend.
Run python -m unittest in the same folder. unittest is Python's built-in test tool, which we met in the dependency injection post:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Each dot is one test that passed. Nothing was printed and nothing was sent. (The time on your machine may say 0.001s.)
Where does Python itself use factories?
You've already used a few without the name:
logging.getLogger(name). A logger is the object your code writes log messages through. The logging docs are firm about it: "Note that Loggers should NEVER be instantiated directly, but always through the module-level function logging.getLogger(name)." That's a factory with a bonus: "Multiple calls to getLogger() with the same name will always return a reference to the same Logger object." So the factory also decides whether to build a new object or hand back one it already made.collections.defaultdict. A dictionary that builds a value when a key is missing. Its docs even use the word: "The first argument provides the initial value for the default_factory attribute; it defaults to None." Indefaultdict(list), the classlistis the factory, and it's called to make an empty list for each new key.- Every class. As the tutorial said, calling a class is calling a function that returns a new object. That's why
NullNotifiercould go straight into our menu.
Factory vs strategy pattern: what's the difference?
These two are easy to mix up, because they often appear together, and our example already has both.
- A factory answers "which object do I make?" It runs, builds something, and its job is done.
make_notifieris a factory. - A strategy answers "which way do I do this step?" It's the swappable piece of behaviour itself, used through one shared method. Our three notifiers are strategies for "send a message": the order service calls
sendand doesn't care which one it got.

send is called.So the factory picks a strategy, and the strategy does the work. You can have strategies without a factory (main.py picks one by hand) and a factory that builds things that aren't strategies at all (one that only makes database connections, say). The next post in this series is about the strategy pattern itself.
When should you not use the factory pattern?
A factory is a small piece of code, but it's still a place a reader has to jump to. Skip it when:
- There's only one class. A
make_notifier()that always returns anEmailNotifieris just a longer way to writeEmailNotifier(sender="orders@letsbug.in"). There's no choice to make, so there's nothing for a factory to do. Add one on the day a second kind arrives. - The
ifchain lives in one place and is used once. Moving a three-branchiffrom the one function that needs it intofactory.pygives you an extra file and an extra jump, and nothing else. The factory pays off when the choice is copied, or when it's likely to grow. Wait for the second copy. - You're tempted to build a factory class with one
createmethod. In Python, a function or a dictionary does the same job, because classes and functions can be passed around and called. Writemake_notifier, notNotifierFactory().create().
What mistake do beginners make with factories?
A silent default. It's tempting to make the factory "safe" by never failing:
def make_notifier(channel):
return BUILDERS.get(channel, NullNotifier)() # unknown name? send nothing
Now make_notifier("SMS"), with a capital S, quietly returns a NullNotifier. The app keeps running, the user never gets a message, and nothing in your logs says why. It's the same bug the else in orders_before.py had, moved into the factory. A factory sits where settings from users and files come in, so it's exactly where a wrong name should stop with a clear error. Keep the "off" case as a name on the menu, and let every name that isn't on the menu raise ValueError, as ours does. Then a typo shows up the first time that code runs, in a test or in your logs, not weeks later in a support ticket. In our main.py loop, that error would also stop the orders after it, so the best place to catch a bad name is earlier still: check channel in BUILDERS when the user saves their setting, and a bad value is never stored.
1) What does make_notifier("Email") do with our factory.py? What exactly does the error say?
2) The shop adds WhatsApp messages. Which files change, and which don't?
3) With orders_before.py, a user's channel is saved as "SMS". What happens when their order ships? And with orders.py and the factory?
Show the answers
1) It raises ValueError: unknown channel 'Email', expected one of ['email', 'off', 'sms']. Names are matched exactly, capitals included. The list is sorted, which is why 'off' comes before 'sms'.
2) Two files change: notifiers.py gets a new class, and factory.py imports it and gets one new line on the menu. orders.py, main.py and the tests don't change; users just start saving "whatsapp" as their channel.
class WhatsAppNotifier:
def __init__(self, business_number):
self.business_number = business_number
def send(self, to, message):
print(f"WhatsApp from {self.business_number} to {to}: {message}")
# and in factory.py: add WhatsAppNotifier to the import line, then one more line in BUILDERS:
BUILDERS["whatsapp"] = partial(WhatsAppNotifier, business_number="+91 90000 00009")
(In factory.py you'd write it inside the BUILDERS = {...} braces; the last line here is the same thing written as one statement.)
3) With orders_before.py, "SMS" matches neither if, so it falls into the else: a NullNotifier, no message, no error. With the factory, ship stops with ValueError: unknown channel 'SMS', expected one of ['email', 'off', 'sms'], and you find the bad setting the first time it's used.
Questions people ask
What is the factory pattern in simple words?
It's one place in your code whose only job is making objects. Other code asks it for an object by name or setting, such as "the SMS notifier", and gets back a ready-to-use object, without knowing which class it came from or how it was set up. Like ordering at a restaurant counter instead of cooking in the kitchen.
When should I use the factory pattern?
When the same "which class do I build?" choice is made in more than one place, when each class needs different settings to build, or when the choice comes from outside the code, such as a user setting or a config (settings) file. If there's only one class, or the choice is made once in one place, you don't need it.
What is the difference between the factory and strategy patterns?
A factory decides which object to make, then its job is done. A strategy is the swappable behaviour itself, used through one shared method, like the notifiers' send. They often work together: the factory picks which strategy to build from a setting, and the rest of the code uses the strategy without caring which one it got.
Is a Python class already a factory?
In a way, yes. The Python tutorial describes a class as something you can treat like a function that returns a new object. That's why a class can sit in a dictionary and be called later. The factory pattern adds the step before that: choosing which class to call, and with which settings.
Keep going
- Previous in this System Design series: Repository pattern, which hands a class its data store through one front door. A factory is how you'd pick which store to build, a real database or an in-memory one for tests.
- Next: "Strategy pattern", where the notifiers themselves are the star: one method, many ways to do it, swapped without an
if. - Where the factory gets handed in: Dependency injection explained with a real Python example, with the same sockets-and-plugs idea.
Checked on 29 October 2026 against the 7 sources below.
Sources
- Python documentation: Design and History FAQ ("Why isn't there a switch or case statement in Python?")
- Python documentation: functools (
partial) - Python documentation: The Python Tutorial, Classes (class instantiation)
- Python documentation: logging (Logger objects and
getLogger) - Python documentation: collections (
defaultdictanddefault_factory) - Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern (2004), factory methods and factory services
- Martin Fowler: Special Case (Patterns of Enterprise Application Architecture catalog)
Every Python sample in this post was run in Python 3.13 before publishing, and the tests were run against the fake notifier.