Join our WhatsApp Channel
Cart / 0.00$

No products in the cart.

Visit Support Portal
No Result
View All Result
Friday, November 6, 2026
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our ServicesBook Now
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
    • Website Security Checker
    • Video Editing Services
  • Our Softwares
    • JaggoRe AIFree
    • Email ExtractorPaid
    • TimeWell AppFree
Get a Free Quote
  • Login
  • Register
Neuraldemy
Friday, November 6, 2026
Cart / 0.00$

No products in the cart.

No Result
View All Result
Neuraldemy
Get A Free Quote
Home Javascript

Factory Pattern In JavaScript/TypeScript

Neuraldemy by Neuraldemy
March 3, 2026 - Updated on October 23, 2026
in Javascript, NodeJS, System Design, Web Development
Reading Time: 22 mins read
A A
Factory Pattern In JavaScript/TypeScript

In this tutorial, we will learn about factory pattern. Imagine you are building a notification system for a Node.js application. Users can receive notifications through email, SMS, or push notifications. Each channel has its own implementation, but the application should be able to send a notification without needing to understand the internal details of every channel.

A straightforward implementation might look like this:

class NotificationService {
  notifyUser(
    type: string,
    userId: string,
    message: string,
  ): void {
    if (type === "EMAIL") {
      const notifier = new EmailNotification();
      notifier.send(userId, message);
    } else if (type === "SMS") {
      const notifier = new SMSNotification();
      notifier.send(userId, message);
    } else if (type === "PUSH") {
      const notifier = new PushNotification();
      notifier.send(userId, message);
    }
  }
}

This works, but notice that NotificationService has two responsibilities. It decides which notification implementation to use, and it coordinates the actual notification operation.

As more channels are introduced, the service accumulates additional conditions and dependencies. Creating a WhatsApp notification, for example, requires modifying the same service that handles the application’s notification workflow.

The deeper problem is not the if statements themselves. It is that business logic and object-creation logic are coupled together.

The Factory Pattern addresses this design pressure by moving the responsibility of selecting and creating objects into a separate component.

Instead of asking the notification service to construct the appropriate object, we let it request one from a factory.

2. The Core Idea Behind the Factory Pattern

A factory encapsulates object creation behind a method or interface. The caller requests an object, while the factory determines how that object should be constructed.

In our example, the relationship becomes:

           NotificationService
                    |
                    | requests a notifier
                    v
           NotificationFactory
                    |
             Select implementation
              /       |       \
             v        v        v
          Email      SMS      Push
         Notifier  Notifier  Notifier
              \       |       /
               \      |      /
                INotification

The notification service does not need to construct an email, SMS, or push implementation directly. It receives an object that supports the required notification interface and invokes its send() method.

This separates two decisions:

Object creation: Which concrete class should be instantiated?

Business operation: How should the application use the resulting object?

A factory centralizes the first decision so the second can remain focused on its own responsibility.

This is the fundamental idea. The factory does not have to be a complicated framework, nor does it necessarily need inheritance or an elaborate class hierarchy. Its purpose is to give object creation an explicit boundary.

3. Start with a Common Contract

Before implementing the factory, we need a common interface for all notification channels.

interface INotification {
  send(userId: string, message: string): void;
}

The interface establishes the behavior that every notification implementation must provide.

Email, SMS, and push notifications may differ considerably internally, but they all support the same operation: send a message to a recipient.

We can implement that contract in three classes:

class EmailNotification implements INotification {
  public send(userId: string, message: string): void {
    console.log(
      `[EMAIL] Sending to user ${userId}: ${message}`,
    );
  }
}

class SMSNotification implements INotification {
  public send(userId: string, message: string): void {
    console.log(
      `[SMS] Sending to user ${userId}: ${message}`,
    );
  }
}

class PushNotification implements INotification {
  public send(userId: string, message: string): void {
    console.log(
      `[PUSH] Sending to device for user ${userId}: ${message}`,
    );
  }
}

These implementations are deliberately simple, but the separation is meaningful.

EmailNotification can later use an email provider, SMSNotification can integrate with an SMS gateway, and PushNotification can use a mobile push service. Each class owns the implementation details of its channel.

The interface lets the caller depend on a common contract instead of each concrete class.

This is important because a factory becomes much more useful when the objects it creates share a meaningful abstraction. Otherwise, the caller may still need to know the exact class it received and how to use it.

4. Implementing the Factory

Our factory needs to know which notification implementation to create. We will represent the supported options with an enum.

enum NotificationType {
  EMAIL = "EMAIL",
  SMS = "SMS",
  PUSH = "PUSH",
}

The enum gives the selection options a clear, constrained type instead of spreading arbitrary strings throughout the application.

Now we can create the factory:

class NotificationFactory {
  public static createNotification(
    type: NotificationType,
  ): INotification {
    switch (type) {
      case NotificationType.EMAIL:
        return new EmailNotification();

      case NotificationType.SMS:
        return new SMSNotification();

      case NotificationType.PUSH:
        return new PushNotification();

      default:
        throw new Error("Invalid notification type requested");
    }
  }
}

The factory exposes one public method: createNotification().

The method accepts a NotificationType and returns an INotification. Internally, it maps each supported type to a concrete class.

For example:

const notifier = NotificationFactory.createNotification(
  NotificationType.EMAIL,
);

This call returns a new EmailNotification instance, but the variable’s declared type is INotification.

That distinction is intentional. The caller only needs to know that the returned object satisfies the notification contract. It does not need to depend on the concrete email class.

The factory owns the mapping between selection criteria and implementations. If the email integration changes from one provider to another, the factory can continue returning an object that satisfies INotification, while the details of the integration remain inside the relevant implementation.

The default branch provides a defensive failure path if an unsupported value reaches the factory at runtime. TypeScript’s enum type helps prevent invalid calls during normal type checking, but type assertions, untyped JavaScript, or external input can bypass compile-time checks.

One important detail: this implementation creates a new notification object whenever createNotification() is called. A factory does not inherently imply a singleton, object reuse, or caching. Those are separate lifecycle decisions.

5. Connecting the Factory to the Notification Service

With creation encapsulated, the service becomes simpler:

class NotificationService {
  public notifyUser(
    type: NotificationType,
    userId: string,
    message: string,
  ): void {
    const notifier =
      NotificationFactory.createNotification(type);

    notifier.send(userId, message);
  }
}

The service now performs two steps: request the appropriate notifier, then invoke its behavior.

It does not instantiate EmailNotification, SMSNotification, or PushNotification directly. It works through the factory and the shared interface.

Here is the complete usage code:

const myAppService = new NotificationService();

console.log("--- System Alerts ---");

myAppService.notifyUser(
  NotificationType.EMAIL,
  "user_101",
  "Welcome to the platform!",
);

myAppService.notifyUser(
  NotificationType.SMS,
  "user_102",
  "URGENT: Server CPU is at 99%.",
);

myAppService.notifyUser(
  NotificationType.PUSH,
  "user_103",
  "Your data export is ready.",
);

Expected output:

--- System Alerts ---
[EMAIL] Sending to user user_101: Welcome to the platform!
[SMS] Sending to user user_102: URGENT: Server CPU is at 99%.
[PUSH] Sending to device for user user_103: Your data export is ready.

Let’s follow one call through the design.

When the application calls notifyUser() with NotificationType.EMAIL, the service passes that type to the factory. The factory creates an EmailNotification and returns it as an INotification. The service invokes send() on the returned object.

The service does not need an email-specific branch because all implementations honour the same contract. The concrete object’s implementation determines what happens when send() is called. This is polymorphism working together with the factory.

6. What Has Actually Improved?

The primary improvement is the separation of object creation from business coordination.

In the original design, NotificationService knew about every concrete class and the conditions required to instantiate it. In the factory-based version, that knowledge moves into NotificationFactory.

This reduces the notification service’s direct dependencies on concrete implementations and makes its primary job clearer.

It also creates a focused place to change construction rules. For example, a factory could validate configuration, select a provider based on environment settings, or decide between different implementations of the same contract.

However, we should be precise about what this design achieves.

The factory centralizes creation, but it does not remove every dependency on notification types. NotificationService still accepts NotificationType, and the factory still needs to know which concrete class corresponds to each value.

That is a perfectly reasonable trade-off for a small system. The goal is not to eliminate all dependencies. It is to put each responsibility in a deliberate place and avoid making one component responsible for everything.

7. The Engineering Trade-off: Adding a New Notification Channel

Suppose the product now needs WhatsApp notifications.

With this design, we would add a WhatsAppNotification class implementing INotification, add a WHATSAPP member to NotificationType, and add another case to NotificationFactory.

The notification service itself does not need another channel-specific branch.

That is an improvement, but the factory must still change whenever a new type is added. Therefore, this particular design is not fully closed to modification when extending notification types. It centralizes the change rather than eliminating it.

For three or four stable options, an enum and switch statement may be the clearest solution. It is easy to read, easy to debug, and makes the supported creation paths explicit.

If implementations are added frequently, supplied by plugins, or selected dynamically from configuration, the factory can evolve into a registry or be replaced by dependency injection. Those approaches allow registrations to be managed separately from the selection logic.

The important engineering question is not whether a switch statement is inherently bad. It is whether the number and frequency of variations justify a more flexible creation mechanism.

8. Where Is the Factory Pattern Used?

The pattern is useful whenever a caller needs an object, but should not be responsible for deciding how its concrete implementation is constructed.

In a notification system, a factory can select an email, SMS, or push adapter based on the requested channel. The same approach can be applied to payment processing, where a factory creates the appropriate payment provider implementation, or to storage systems that support local files, cloud object storage, or different database backends.

Factories are also useful in systems that need to select parsers, exporters, report generators, or message handlers based on an input format or event type.

The shared characteristic is that the application has a family of alternative implementations, and a separate component is responsible for selecting or constructing the appropriate one.

Factories are especially helpful when construction involves more than a simple new expression: configuration, credentials, provider setup, or multiple related dependencies may need to be assembled before the object is ready.

If a class is trivial to instantiate and there is no meaningful creation decision to encapsulate, introducing a factory may add indirection without improving the design.

9. Simple Factory vs Factory Method

One terminology distinction is worth understanding when studying low-level design.

The implementation in this article is commonly called a Simple Factory. It has one factory method that uses a selection value to decide which concrete class to instantiate.

In strict design-pattern terminology, it is not the classic Factory Method Pattern described by the Gang of Four. Factory Method typically delegates the creation decision to subclasses that override a factory method. It is related to the broader family of factory-based techniques, but it uses a different structure.

The classic Abstract Factory Pattern addresses another problem: creating families of related objects without tying the client to their concrete classes. For instance, a system might need a coordinated set of email-provider components, such as a message sender and a template renderer.

These patterns share the goal of encapsulating object creation, but they are not interchangeable. Our example intentionally uses the simpler approach because one selection value is enough to choose one notification implementation.

10. A Production Consideration: Construction Is Not the Same as Delivery

The sample code uses console.log() so the selection and dispatch flow is easy to see. In a production application, sending a notification usually involves network I/O and can fail.

The interface might therefore return a promise:

interface INotification {
  send(userId: string, message: string): Promise<void>;
}

The service could then await the result and handle delivery failures. The factory’s responsibility would still be the same: select or construct an implementation that satisfies the interface.

This distinction helps keep the design clean. The factory should not become the place where notification delivery, retry policies, business rules, and error handling all accumulate simply because it already knows the concrete classes.

Creation, delivery, and reliability are related concerns, but they are not the same responsibility.

Conclusion

The Simple Factory approach begins with a practical design problem: the component coordinating a task should not also need to know every detail of constructing the objects that perform it.

In our notification system, INotification defines a shared contract, the email, SMS, and push classes implement that contract, NotificationFactory decides which implementation to create, and NotificationService coordinates the operation without constructing concrete notification classes itself.

The result is a clearer responsibility boundary and a central place to manage the creation decision. The trade-off is that the factory still needs updating when the set of supported types changes, which is often acceptable for a small and stable set of options.

When applying this pattern in low-level design, focus on the underlying question: is object creation a decision that should belong to the caller, or is it a responsibility worth encapsulating behind a factory?

Support

Buy author a coffee

Support
SummarizeSendShareTweetScan
Previous Post

Inside OpenAI Military Deal and the Ousting of Anthropic

Next Post

Strategy Pattern In JavaScript/TypeScript

Neuraldemy

Neuraldemy

This is Neuraldemy support. Subscribe to our YouTube channel for more.

Related Posts

The Node.js Process Object and Environment Configuration Explained

Node.js EventEmitter Explained: Events, Listeners, and Event-Driven Architecture

DNS And TCP in Node.js Explained

HTTPS and TLS in Node.js Explained : Encryption, Certificates, Handshakes, and Secure Servers

HTTP Explained: History, Protocol Internals, and Building a Web Server with Node.js

Node.js Streams Explained: Readable, Writable, Transform, and Backpressure

Next Post
Strategy Pattern In JavaScript/TypeScript

Strategy Pattern In JavaScript/TypeScript

FormData API: A Beginner’s Guide to Native Validation

FormData API: A Beginner’s Guide to Native Validation

100 CSS Interview Questions

100 CSS Interview Questions

Support

Buy author a coffee

Support

Support Neuraldemy

Newsletter

Top rated products

  • Probability and Statistics for Machine Learning and Data Science Probability and Statistics for Machine Learning and Data Science
    Rated 5.00 out of 5
    30.00$ Original price was: 30.00$.12.99$Current price is: 12.99$.
  • SVM Notes: Optimization & Implementation SVM Notes: Optimization & Implementation
    Rated 5.00 out of 5
    9.99$ Original price was: 9.99$.4.99$Current price is: 4.99$.
  • spidy mail hunter software Spidy Mail Hunter: Powerful And Intelligent Website & PDF Email Extractor Software (Windows App)
    Rated 4.78 out of 5
    149.00$ Original price was: 149.00$.49.00$Current price is: 49.00$.
  • Linear Algebra For Machine Learning And Data Science Linear Algebra For Machine Learning And Data Science
    Rated 4.71 out of 5
    40.00$ Original price was: 40.00$.20.99$Current price is: 20.99$.
  • Starter Website Package Infographic Starter Business & Creator Website Package (3 Pages + 1 Year Free Hosting) 199.00$ Original price was: 199.00$.149.00$Current price is: 149.00$.

Recent Posts

The Node.js Process Object and Environment Configuration Explained

Node.js EventEmitter Explained: Events, Listeners, and Event-Driven Architecture

DNS And TCP in Node.js Explained

November 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
30  
« Oct    

Neuraldemy

Neuraldemy

Neuraldemy

Software Development

Neuraldemy helps you learn ML, AI, Web Dev and data science from scratch. Addtionally, we also provide web development and hosting services for businesses, and individuals

[convertkit form=7049059]
  • Get Started
  • Contact
  • Book Services
  • Privacy Policy
  • Terms Of Use
  • Support Portal
  • Managed Hosting Terms
  • Web Development Terms
  • Refund Policy
Neuraldemy

© 2026 - A learning platform by Odist Magazine

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms below to register

*By registering into our website, you agree to the Terms & Conditions and Privacy Policy.
All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
Join Our WhatsApp Channel
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our Services
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
    • Website Security Checker
    • Video Editing Services
  • Our Softwares
    • JaggoRe AI
    • Email Extractor
    • TimeWell App
  • Login
  • Sign Up
  • Cart
Order Details

© 2026 - A learning platform by Odist Magazine

This website uses cookies. By continuing to use this website you are giving consent to cookies being used.
0