Factory Method Design Pattern in TypeScript: A Beginner’s Guide
26 Aug 2025
Table of contents
- Prerequisites
- Introduction
- What is Factory Method Pattern
- Basic Implementation
- Bad code vs Factory method pattern
- When to use it
- When not to use it
- Real world use cases
- Conclusion
- Reference
Prerequisites
These are the basic prerequisites for this blog. If you’re not familiar with first two points. this blog might not be suitable for you. Otherwise, you’re good to go! The examples are really simple and can be easily replicated in other languages.
- Basic understanding of OOP concepts
- Basic understanding of UML diagrams
- Basic Typescript knowledge
Introduction
Hey devs, Finally i came up with my first blog on design patterns. Today we are going to learn factory method pattern. Yeah, you heard me right As I mentioned in importance of design patterns we are going to learn it together. In this entire design pattern series i will be using an application called Ticket booking system. Here, at the center we are having Booking, which connects Customer with a Resource for a specific time slot. Each booking has an ID, and the ID of the resource, the ID of the customer, the time slot chosen and a status. The Resource could be anything that can be reserved - a room in a hotel, a seat in theatre, or even a vehicle. The Customer is the person making the booking. To support the process, there’s a Payment part that handles money for the booking, an AvailabilityService that checks if a resource is free at a given time, and a NotificationService that sends updates or reminders to customers. Put together, these pieces form a compact system where customers can book resources, pay for them, and get notified about their reservations. We will use this application to decide which problem needs which specific pattern and also where we dont want to use a design pattern. I think that’s enough for the introduction let’s get into little bit of theory.
What is Factory Method Pattern
Factory Method
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
Problem
Imagine in our booking platform. In the first version, the app only supports payments through Stripe, so most of the payment-related code is directly tied to the StripeProcessor
class. Over time, the platform grows and stakeholders decided to include PayPal support. It sounds good right, but here’s the catch: since the code is tightly coupled to StripeProcessor
, adding PayPal means we’ll have to touch multiple parts of the codebase. And if later if we want to add even more options like Apple Pay or Google Pay, the same problem repeats.
Without a good design, the code quickly becomes messy, full of conditionals that check “which payment type is this?” before deciding how to handle it.
Solution
This is where the Factory Method comes in. Instead of directly creating payment processors all over the place, we delegate that responsibility to a PaymentFactory
with a factory method PaymentFactory.createPaymentProcessor()
, now this method will return a payment type StripeProcessor
, PaypalProcessor
, or any future payment type. The client code will use the instance without conditionals and switch statements.
Factory Method Diagram
Before understanding the solution we have to learn some terms. Let’s start with Product it is an interface which will be returned by the creator and its subclasses. Then, the Concrete Products these are different implementations of the product interface. Next the Creator class which declares the factory method that returns new product objects. It’s important that the return type of this method matches the product interface. Next, the Concrete Creator classes these will override the base factory method. Finally the Client Code it is the code that uses the Creator and Products without depending on their concrete classes. Now look at the solution we created using the factory method pattern
Payment Factory
The Factory Method pattern suggests that instead of creating payment processors directly in the client code (using new StripeProcessor()
or new PayPalProcessor()
), we delegate that responsibility to a dedicated factory method. The objects are still created with the new
operator under the hood, but this call is now wrapped inside the factory. The objects returned by the Creator’s Factory method are often referred to as Products.
At first, this may look like an unnecessary change. After all, we’re just moving the new
call from one place to another. But here we are centralizing object creation in a factory, we can easily switch or extend the payment processors without touching the rest of the booking payment logic.
The only requirement is that all payment processors implement a common interface, in this case PaymentProcessor
, which declares a method callPaymentApi()
. Both StripeProcessor
and PayPalProcessor
implement this interface, but each one knows how to talk to its own payment gateway.
The PaymentFactory
defines the factory method createPaymentProcessor()
. Subclasses such as StripePaymentFactory
and PayPalPaymentFactory
override this method and decide which concrete processor to return. The client code that triggers a payment doesn’t need to know if it’s dealing with Stripe, PayPal, or some new processor we add in the future. It just asks the factory for a PaymentProcessor
and calls callPaymentApi()
. Thanks to the shared interface, the client doesn’t need to worry about the details of each implementation it just knows that the payment will be processed.
This way, the booking platform stays clean and flexible. Adding support for a new payment provider no longer requires touching the booking flow. You only need to add a new processor class and extend the factory the rest of the system remains unchanged.
Basic Implementation
PaymentProcessors.ts
interface PaymentProcessor {
// Sample method to call the payment api
callPaymentApi(amount: number): void;
}
class StripeProcessor implements PaymentProcessor {
callPaymentApi(amount: number) {
console.log(`Processing ${amount} with Stripe`);
}
}
class PayPalProcessor implements PaymentProcessor {
callPaymentApi(amount: number) {
console.log(`Processing ${amount} with PayPal`);
}
}
FactoryCreators.ts
abstract class PaymentFactory {
// Factory method
abstract createPaymentProcessor(): PaymentProcessor;
// Other business logics related to payment
}
class StripePaymentFactory extends PaymentFactory {
createPaymentProcessor(): PaymentProcessor {
return new StripeProcessor();
}
}
class PayPalPaymentFactory extends PaymentFactory {
createPaymentProcessor(): PaymentProcessor {
return new PayPalProcessor();
}
}
// Future payment processors
Application.ts
export class Application {
private factory: PaymentFactory;
constructor(config: string) {
if (config === "stripe") {
this.factory = new StripePaymentFactory();
} else if (config === "paypal") {
this.factory = new PayPalPaymentFactory();
} else {
throw new Error("Unsupported payment provider");
}
}
run(amount: number) {
const processor = this.factory.createPaymentProcessor();
processor.callPaymentApi(amount);
}
}
index.ts
import { Application } from "./Application";
const CONFIG = "paypal"; // In a real-world scenario, this would come from a config file, env var, or user input
const app = new Application(CONFIG);
app.run(500);
Bad code vs Factory method pattern
BadCode.ts
// Concrete payment processors
class StripeProcessor {
callPaymentApi(amount: number) {
console.log(`Processing ${amount} with Stripe`);
}
}
class PayPalProcessor {
callPaymentApi(amount: number) {
console.log(`Processing ${amount} with PayPal`);
}
}
// client code
function makePayment(type: string, amount: number) {
if (type === "stripe") {
const processor = new StripeProcessor();
processor.callPaymentApi(amount);
} else if (type === "paypal") {
const processor = new PayPalProcessor();
processor.callPaymentApi(amount);
} else {
throw new Error("Unsupported payment type");
}
}
// Usage
makePayment("stripe", 100);
makePayment("paypal", 50);
The problem with this approach is that the booking logic ends up bloated with lot of if/else
checks. Every time we want to add a new payment option, like Apple Pay, we have to come back and hack more conditions into the function. On top of that, the client code knows way too much about the actual payment processors like Stripe, PayPal, whatever. It feels messy and tightly coupled, which is exactly the kind of thing that makes code harder to maintain in the long run.
On the other hand the factory method pattern setup have a super clean client code setup. No messy if/else
branches. If we ever need to support a new payment option, we just create a new PaymentProcessor
and its factory. That’s it we don’t have to touch the existing booking flow at all. And since the booking logic only talks to the PaymentProcessor
interface, it doesn’t care whether the processor is Stripe, PayPal, or something brand new. The code stays flexible, decoupled, and way easier to maintain.
When to use it
- We need to use the Factory Method when we don’t know upfront the exact types the code will need to handle. In this case, we had no idea which payment providers the booking platform would eventually support. Today it’s Stripe, tomorrow it might be PayPal, Google Pay, or something that doesn’t even exist yet. That makes Factory Method a great fit.
- It’s also useful if we’re building something that other developers might extend. By exposing a factory method, we give them a clean entry point to plug in their own custom logic.
- Another subtle benefit is it keeps the core flow focused on business logic instead of drowning in object creation details. The booking just cares about process this payment the factory decides which processor to spin up.
When not to use it
- Don’t use this pattern if we already know exactly which types we’ll be working with and that will never change. If the booking platform was forever tied to Stripe and nothing else in future, introducing a factory would just add unnecessary complexity.
- Also, Factory Method comes with some overhead. Every new type usually means creating another subclass to handle it. That’s fine if variability is expected, but if we’re absolutely certain about the system will never grow beyond a fixed set of cases, then it’s over-engineering.
- Another trap is using it just for the sake of showing off a design pattern. If object creation is already simple and stable, throwing a factory on top doesn’t make the code smarter. It actually makes it harder to read.
Real world use cases
- I’ve personally used the Factory Method pattern in one of the business flows I worked on. The requirement was to handle multiple types of user subscriptions. Each subscription type triggered a different service API call, and I implemented this using the Factory Method. Instead of writing a bunch of
if/else
checks, the factory neatly created the right service handler depending on the subscription. - You might have seen a similar pattern in the Bot Framework SDK, especially when it comes to managing user and conversation state storage. For example,
UserState
andConversationState
act like the products, while classes likeMemoryStorage
,UserMemory
, andConversationMemory
act as the creators. ThecreateStorage
method is the factory method. The SDK actually mixes in a few related patterns as well. It might have lot of use cases but I explored only these two as of now.
Conclusion
Ok devs, that’s it for today. I tried my best to explain this pattern. if you have any queries are suggestions please feel free to reach me out.
Code, learn, refactor, repeat