Most of the time, creating an object is boring. You call the constructor, you get the thing:
user = User("mamba")Nothing to see here, and no pattern required. The Factory pattern earns its place only when choosing which object to build becomes a real decision - one with branching logic that you don't want smeared across your codebase.
The problem it solves
Say you send notifications through several channels, and which class you build depends on runtime data:
if channel == "email":
notifier = EmailNotifier(smtp_config)
elif channel == "sms":
notifier = SmsNotifier(twilio_client)
elif channel == "push":
notifier = PushNotifier(firebase_app)
else:
raise ValueError(f"unknown channel: {channel}")
notifier.send(message)On its own, fine. The trouble is that this block tends to multiply. You need a notifier when a user signs up, when an order ships, when a password resets. Copy this if/else into all three places and you have three copies of the construction logic. Add a new channel and you edit every copy, and the day the SMS constructor gains a new argument, you go hunting for all of them.
The real issue: knowledge of how to build a notifier is leaking into every place that uses one. Those are two different jobs.
The fix: give creation one home
A factory is just a function (or a class) whose only responsibility is to make the right object. Pull the decision out once:
def make_notifier(channel):
if channel == "email":
return EmailNotifier(smtp_config)
if channel == "sms":
return SmsNotifier(twilio_client)
if channel == "push":
return PushNotifier(firebase_app)
raise ValueError(f"unknown channel: {channel}")Now every caller is one clean line, and none of them knows or cares how a notifier gets built:
notifier = make_notifier(user.preferred_channel)
notifier.send(message)The construction logic exists in exactly one place. Add a Slack channel and you edit one function. Change how SmsNotifier is wired up and every caller gets it for free, because none of them ever knew the details.
Notice this is a plain function. In Python you rarely need the ceremony of a NotifierFactory class with a create() method - a module-level function is the same pattern with less noise. Use a class only when the factory itself needs to hold state or configuration.
What you actually gain
Callers depend on the abstraction, not the concretes. The code that sends a message depends on "some notifier," not on EmailNotifier specifically. Swap the whole set of implementations and the callers don't change.
Construction can get smart without spreading. Factories are the natural home for logic like "read a config object," "look up which class from a registry," or "return a cached instance." All of that lives behind one door instead of leaking outward.
One place to change. New type, changed constructor, different default - it's a single edit, not a search-and-replace across the codebase.
A cleaner variant: the registry
Once you have a few types, even the if/else inside the factory can go. Map the key to the thing that builds it:
BUILDERS = {
"email": lambda: EmailNotifier(smtp_config),
"sms": lambda: SmsNotifier(twilio_client),
"push": lambda: PushNotifier(firebase_app),
}
def make_notifier(channel):
builder = BUILDERS.get(channel)
if builder is None:
raise ValueError(f"unknown channel: {channel}")
return builder()Now adding a channel is one dictionary entry, and the factory function itself never changes. This pairs beautifully with the Strategy pattern - Strategy picks the behaviour, a factory builds it.
When you don't need a factory
Be honest with yourself before reaching for this.
If construction is a single obvious constructor call, just call it. User("Preetam") does not need a make_user wrapper. Wrapping trivial construction in a factory is pure ceremony that hides nothing.
If you build the object in exactly one place, leave it there. The factory pays off when the same creation decision happens in several spots. One caller means there's nothing to centralise yet.
The pattern earns its keep when creation is a decision, and that decision would otherwise be duplicated. Short of that, new really is enough.
The takeaway
The Factory pattern isn't about objects, it's about separating the decision of what to build from the code that uses the result. When choosing a type involves branching, config, or setup that keeps showing up in multiple places, give that choice one home. Your callers shrink to a single line, and the next new type becomes a one-line change instead of a scavenger hunt.

