The Factory Pattern in JavaScript: One Name, Three Designs

By Sergey Nosov

13 October 2026

“Use a factory here” sounds like precise advice in a code review. It is not, because three different designs share the name. Here is the most familiar of them, a class that turns a role code into a user so that the code that needs a user never calls a constructor:

class User {
  constructor(name, role) {
    this.name = name;
    this.role = role;
  }
}

class UserFactory {
  static createUser(name, role) {
    switch (role) {
      case "admin":
        return new User(name, "Administrator");
      case "editor":
        return new User(name, "Editor");
      case "viewer":
        return new User(name, "Viewer");
      default:
        throw new Error(`Unknown role: ${role}`);
    }
  }
}

const admin = UserFactory.createUser("Alice", "admin");
// Prints: Administrator
console.log(admin.role);

It is easy to take this for the Factory pattern from Design Patterns: Elements of Reusable Object-Oriented Software. Its authors, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, are known as the Gang of Four, and the book appeared in 1994. It contains no pattern by that name. Of its twenty-three patterns, two have “factory” in the name, Factory Method and Abstract Factory, and the class above is neither. Head First Design Patterns (2004) calls it the Simple Factory, “more of a programming idiom” than a pattern, and notes that some developers mistake it for the “Factory Pattern.”

The names matter because the designs answer different questions. A simple factory keeps the choice of class in one function. Factory Method lets a subclass make that choice for a base class that cannot know it. Abstract Factory hands out a family of objects that must match. Confuse them, and you pay for a pattern without its benefit. You get a switch that still needs an edit for every new type, or a class hierarchy that exists only to create one object. This article takes the three in turn, quoting the books that describe them, with samples that run on Node.js 24.

Why Not Just Use a Constructor?

Most objects should be created with new or an object literal, and a factory needs a reason to exist. The Gang of Four give the general one as the first item in a list of common causes of redesign. “Specifying a class name when you create an object commits you to a particular implementation instead of a particular interface,” they write. “This commitment can complicate future changes.” Their remedy follows: “To avoid it, create objects indirectly.”

In JavaScript, the interface is implicit. An object fits wherever it has the methods its caller uses, as I discussed in SOLID in JavaScript. A line like new TextLogger() in the middle of business code commits that code to one implementation. While text is the only log format you need, the commitment costs nothing. It starts to cost when a second implementation appears, such as JSON logs for a log collector or a silent logger for tests.

A named creation function, which MDN’s guide to classes calls a factory function, has advantages before any pattern is involved. Joshua Bloch makes the case for Java in the first item of Effective Java, “Consider static factory methods instead of constructors.” Several of his reasons carry over to JavaScript. In Java, unlike constructors, static factory methods:

A JavaScript constructor can fake the last two by returning some other object, which then replaces the one new created. The trick confuses subclasses: their constructors set fields on the substitute, and new can return an object that is not an instance of the subclass at all. A named function makes the substitution explicit. JavaScript also adds a reason of its own: a constructor cannot be async, so an object that needs awaited setup is better built by an async factory. Node.js does this for files. You get a FileHandle by awaiting open() from node:fs/promises, not by calling a constructor.

Bloch also flags the naming confusion this article is about: “Note that a static factory method is not the same as the Factory Method pattern from Design Patterns.”

The Simple Factory: One Place to Decide

Look again at the user factory from the opening. Every branch calls the same constructor, and only the title changes, so a Map from role codes to titles would do the same job with less code. A switch like this one earns its place when the class itself varies. Logging is a common case. A developer wants readable text, a log collector wants JSON, and a test wants silence:

class TextLogger {
  log(level, message) {
    console.log(`${level.toUpperCase()} ${message}`);
  }
}

class JsonLogger {
  log(level, message) {
    console.log(JSON.stringify({ level, message }));
  }
}

class SilentLogger {
  log() {}
}

// The only code that names a class
function createLogger(format) {
  switch (format) {
    case "text":
      return new TextLogger();
    case "json":
      return new JsonLogger();
    case "silent":
      return new SilentLogger();
    default:
      throw new Error(`Unknown log format: ${format}`);
  }
}

// Configuration picks the class
const logger = createLogger(process.env.LOG_FORMAT || "text");
// Prints: INFO Server started
logger.log("info", "Server started");

The rest of the program asks for a logger and never names a class. Setting LOG_FORMAT=json switches every log line to JSON without touching the code that logs. An unknown format fails at startup with an error that names it, instead of returning undefined and crashing somewhere else later. Unlike the user factory, this one is a plain function: in JavaScript, a class with nothing but a static method could just as well be one.

The book describes this shape as a variation of Factory Method, under the name parameterized factory methods. “The factory method takes a parameter that identifies the kind of object to create,” the authors write. In their version, the method belongs to a class, and “Overriding a parameterized factory method lets you easily and selectively extend or change the products that a Creator produces.” A standalone function like createLogger has no such hook, and that hook is the difference between the idiom and the pattern.

JavaScript platforms are full of simple factories. In an HTML document, document.createElement("input") returns an HTMLInputElement, and document.createElement("foo") returns an HTMLUnknownElement. A factory can also hide a class that never varies. In Node.js, crypto.createHash() returns a Hash for every algorithm, and the documentation is explicit: “Hash objects are not to be created directly using the new keyword.” Since Node.js 22, calling the constructor prints a deprecation warning. The factory keeps the class a detail the platform is free to change.

A simple factory has two limits. The first concerns the open-closed principle. Every new format means another edit to the switch, which reopens a function that already works. JavaScript offers a way out, because a class is a value. A Map from names to classes can replace the switch:

// Format names map to classes
const loggerClasses = new Map();

function registerLogger(format, Logger) {
  if (loggerClasses.has(format)) {
    throw new Error(`Format already registered: ${format}`);
  }
  loggerClasses.set(format, Logger);
}

function createLogger(format) {
  const Logger = loggerClasses.get(format);
  if (!Logger) throw new Error(`Unknown log format: ${format}`);
  return new Logger();
}

// Registered at the entry point
registerLogger("text", TextLogger);
registerLogger("json", JsonLogger);
registerLogger("silent", SilentLogger);

// Prints: WARN Disk almost full
createLogger("text").log("warn", "Disk almost full");

A new format is now a new class plus one registration line, and createLogger does not change. The registry also rejects a duplicate name, so a second registration cannot quietly replace the first. The Gang of Four describe storing classes this way in languages that treat classes as objects, such as Smalltalk. “You can think of a class in these languages as a degenerate factory that creates only one kind of product,” they write. The registry is a collection of such factories, and the SOLID article makes the same move for discount rules.

The second limit is testing. A factory does not, by itself, make the code that calls it easy to test. A function that calls createLogger is nearly as hard-wired as one that calls new TextLogger(). To give it a fake, a test must change the environment or replace the module. If the code needs one logger, pass it the logger. Pass a factory only when the code must create objects itself, at a time or in a number it decides, such as one object per request.

Factory Method: Let the Subclass Decide

Factory Method is one of the two factory patterns the Gang of Four did name. Its intent reads: “Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.” Its other name is Virtual Constructor.

Its classic home is a framework. A framework defines how objects work together and “is often responsible for creating these objects as well,” the authors write. Yet it cannot know the classes an application will define. So the framework calls a method to create each object, and the application overrides that method. Here is a small framework for importing JSON Lines, a format with one JSON value per line. It owns the loop, the parsing, and the error messages, and it leaves one decision to subclasses:

// The framework owns the loop
class Importer {
  import(text) {
    const records = [];
    const lines = text.split("\n");
    // Allow one trailing newline
    if (lines.at(-1) === "") lines.pop();
    for (const [i, line] of lines.entries()) {
      try {
        records.push(this.createRecord(JSON.parse(line)));
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        throw new Error(`Line ${i + 1}: ${message}`, { cause: error });
      }
    }
    return records;
  }

  // The factory method
  createRecord(data) {
    throw new Error("Subclasses must override createRecord");
  }
}

class Customer {
  constructor(id, name) {
    if (typeof name !== "string") throw new Error("name is required");
    this.id = id;
    this.name = name;
  }
}

// The subclass picks the class
class CustomerImporter extends Importer {
  createRecord(data) {
    return new Customer(data.id, data.name);
  }
}

const importer = new CustomerImporter();
const [first] = importer.import('{"id":1,"name":"Ada"}');
// Prints: true
console.log(first instanceof Customer);

try {
  importer.import('{"id":1,"name":"Ada"}\n{"id":2}');
} catch (error) {
  // Prints:
  // Line 2: name is required
  console.log(error.message);
}

The base class never names Customer, which is the benefit the book claims: “Factory methods eliminate the need to bind application-specific classes into your code.” The same loop can import orders or products, and whatever a subclass creates, a bad line reports its line number. A blank line counts as a bad one, since the format allows none; only a final line break is skipped.

Two sentences after that benefit, the book names a potential disadvantage: “clients might have to subclass the Creator class just to create a particular ConcreteProduct object.” That is fine, it adds, when the client has to subclass anyway. Here, CustomerImporter exists only to create, and in JavaScript, you can usually skip such a subclass. Functions and classes are values, so the creation step can arrive as an argument:

class Importer {
  #createRecord;

  // The creation step is passed in
  constructor(createRecord) {
    this.#createRecord = createRecord;
  }

  // Still the factory method
  createRecord(data) {
    return this.#createRecord(data);
  }

  // The rest is unchanged
}

const customers = new Importer(
  (data) => new Customer(data.id, data.name),
);
const orders = new Importer(
  (data) => new Order(data.id, data.total),
);

The factory method survives: a subclass that overrides createRecord still works. But the choice of class now arrives from outside, and a new record type costs one argument instead of one class. That is the other route the Gang of Four describe, one that “relies more on object composition.” In their words: “Define an object that’s responsible for knowing the class of the product objects, and make it a parameter of the system.” Their Factory Method chapter even records a Smalltalk variant close to this one. It stores the class to create in a variable, so that “you don’t have to subclass Application to vary the product.”

Static methods offer one more route. Inside a static method, this is the class the method was called on. A static factory that calls new this() therefore builds whichever subclass it is called on, as long as that subclass’s constructor accepts the same arguments. MDN calls Array.from() “a generic factory method”: called on a subclass of Array, it will “return new instances of the subclass instead of Array instances.”

Abstract Factory: Keep a Family Together

Abstract Factory, also known as Kit, solves a different problem. Its intent: “Provide an interface for creating families of related or dependent objects without specifying their concrete classes.” The book’s example is a user interface toolkit that supports several look-and-feel standards. Widgets such as scroll bars, windows, and buttons must all follow one standard, so the application creates every widget through one factory.

In JavaScript, such a factory needs no abstract class. It can be an object whose properties are creation functions, with each family as one such object. In the sample below, two families build the parts of a welcome email, one for the message’s HTML part and one for its plain-text part. Each product is a small object with a render method; in a user interface library, it would be a component with state of its own.

const escapeHtml = (text) =>
  String(text).replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);

// One family writes HTML...
const htmlKit = {
  createHeading: (text) => ({
    render: () => `<h1>${escapeHtml(text)}</h1>`,
  }),
  createButton: (label, url) => ({
    render: () =>
      `<a class="button" href="${escapeHtml(url)}">${escapeHtml(label)}</a>`,
  }),
};

// ...and one writes plain text
const textKit = {
  createHeading: (text) => ({
    render: () => `${text}\n${"=".repeat(text.length)}`,
  }),
  createButton: (label, url) => ({
    render: () => `${label}: ${url}`,
  }),
};

// The client never names a format
function welcomeEmail(kit, name) {
  const parts = [
    kit.createHeading(`Welcome, ${name}`),
    kit.createButton("Log in", "https://example.com/app"),
  ];
  return parts.map((part) => part.render()).join("\n\n");
}

const message = {
  html: welcomeEmail(htmlKit, "Ada"),
  text: welcomeEmail(textKit, "Ada"),
};
// Prints:
// Welcome, Ada
// ============
//
// Log in: https://example.com/app
console.log(message.text);

Called with htmlKit, welcomeEmail returns the HTML part; called with textKit, the plain-text part. A password-reset email or a receipt is one more function like it, and it works with either kit. One caution for anyone who copies the HTML kit: escaping is not the whole defense. It leaves a javascript: link intact, so a URL that comes from input also needs its scheme checked.

The families must not mix. An HTML button in the plain-text part shows the reader raw tags, and a plain-text heading in the HTML part carries unescaped text, a bug and possibly a security hole. Passing a single kit makes a mix hard to write. The book lists this among the pattern’s consequences: “It promotes consistency among products.”

The same section names the price: “Supporting new kinds of products is difficult.” Add a table to the emails, and every kit needs a createTable function, including any kit a test defines. New families are cheap, and new kinds of products are expensive, so the pattern fits where the set of products is stable and the families vary.

If the kit collected the parts and returned the finished email at the end, it would be a Builder. Builder, as the authors put it, “focuses on constructing a complex object step by step.” An abstract factory hands back each product at once, and the client assembles them.

Real systems use the pattern where the families are platforms. React’s reconciler, an experimental package for building custom renderers, takes a “host config” from each renderer; React DOM, React Native, and React ART each have their own. Two of the host config’s methods create the nodes. One, createInstance, “should return a newly created node”; the README notes that the DOM renderer “would call document.createElement(type) here.” The other, createTextInstance, does the “same as createInstance, but for text nodes.” The reconciler names no node class, so the same code can drive every host.

If a client needs each product only once, skip the factory. Create the products at the entry point and pass them in. An abstract factory earns its place when the client must create products itself, as welcomeEmail does for every message it builds.

Alternatives to a Factory

A factory is one way to create objects indirectly. Before adding one, check whether something else fits better:

A Checklist for Choosing

“Often, designs start out using Factory Method and evolve toward the other creational patterns as the designer discovers where more flexibility is needed,” the Gang of Four observe. I would start one step earlier, with a plain constructor, and move only when a real change hurts. When a review says “use a factory here,” these questions tell you which one, if any:

Further Reading