Factory and Abstract Factory Patterns in JavaScript

· 6 min read · Updated March 17, 2026 · intermediate
javascript design-patterns factory creational

The factory pattern is a creational design pattern that provides a way to create objects without specifying their exact class or constructor. Instead of using new directly, you call a function that handles object creation for you. This gives you flexibility, consistency, and cleaner code. In this tutorial, you’ll learn how factory functions work, how they compare to constructors, and the abstract factory pattern for creating families of related objects.

What Is the Factory Pattern?

A factory function is a regular JavaScript function that returns a new object. Instead of using a constructor with the new keyword, you call a function that encapsulates the creation logic. This pattern is useful when you need to create multiple similar objects with different configurations, or when you want to hide the complexity of object creation.

The key idea is simple: instead of creating objects directly with new, you delegate that responsibility to a function. This function can perform validation, set default values, or apply transformations before returning the final object.

Building a Simple Factory Function

Let’s create user objects with consistent structure:

function createUser(name, email, role) {
  return {
    name,
    email,
    role,
    createdAt: new Date(),
    isActive: true,
    activate() {
      this.isActive = true;
    },
    deactivate() {
      this.isActive = false;
    }
  };
}

const alice = createUser("Alice", "alice@example.com", "admin");
const bob = createUser("Bob", "bob@example.com", "editor");

console.log(alice.role);    // admin
console.log(bob.isActive);   // true

This factory function creates user objects with consistent structure. Every user gets a createdAt timestamp and isActive flag by default. You don’t need to remember to add these properties every time you create a user—they’re handled automatically.

Factory Functions vs Constructor Functions

How do factory functions differ from constructor functions?

// Constructor function
function UserConstructor(name, email) {
  this.name = name;
  this.email = email;
}
const user1 = new UserConstructor("Alice", "alice@example.com");

// Factory function
function createUser(name, email) {
  return {
    name,
    email,
    greet() {
      return `Hello, ${this.name}!`;
    }
  };
}
const user2 = createUser("Bob", "bob@example.com");

Key differences:

  • No new keyword needed: Factory functions work like regular functions, avoiding the confusion between calling with or without new.
  • Flexibility: Factory functions can return anything—not just objects created with this. They can return objects with a different prototype or even primitives.
  • No prototype chain issues: Constructors can have surprising behavior if you forget new. Factory functions don’t have this problem.

However, constructors have their place. They work well with instanceof and are more familiar to developers coming from class-based languages. The choice depends on your use case and team preferences.

Configurable Factories

One powerful aspect of factory functions is the ability to create configurable factories. You can create a factory that accepts options to customize the objects it creates:

function createButton(options = {}) {
  const config = {
    label: options.label || "Click me",
    variant: options.variant || "primary",
    disabled: options.disabled || false,
    onClick: options.onClick || (() => {}),
    // Merge default styles with custom styles
    styles: {
      padding: "10px 20px",
      borderRadius: "4px",
      ...options.styles
    }
  };

  return {
    config,
    render(container) {
      const button = document.createElement("button");
      button.textContent = config.label;
      button.disabled = config.disabled;
      Object.assign(button.style, config.styles);
      button.addEventListener("click", config.onClick);
      container.appendChild(button);
      return button;
    },
    setLabel(newLabel) {
      config.label = newLabel;
    },
    disable() {
      config.disabled = true;
    }
  };
}

// Create different buttons from the same factory
const submitBtn = createButton({
  label: "Submit Form",
  variant: "primary",
  styles: { backgroundColor: "blue", color: "white" }
});

const cancelBtn = createButton({
  label: "Cancel",
  variant: "secondary",
  styles: { backgroundColor: "gray", color: "white" }
});

This pattern is common in UI libraries. The factory provides sensible defaults while allowing callers to customize behavior.

The Abstract Factory Pattern

The abstract factory pattern goes one step further. It provides an interface for creating families of related objects without specifying their concrete classes. This is useful when your code needs to work with multiple families of related objects that should be used together.

Think of it this way: a factory creates one type of product, while an abstract factory creates families of related products. Your code works with the abstract interface, and the specific concrete factory determines what actual objects are created.

// Abstract factory interface
class UIFactory {
  createButton() {
    throw new Error("createButton must be implemented");
  }
  createInput() {
    throw new Error("createInput must be implemented");
  }
}

// Concrete factory for light theme
class LightThemeFactory extends UIFactory {
  createButton() {
    return {
      backgroundColor: "#ffffff",
      color: "#000000",
      border: "1px solid #cccccc"
    };
  }

  createInput() {
    return {
      backgroundColor: "#ffffff",
      color: "#000000",
      border: "1px solid #cccccc"
    };
  }
}

// Concrete factory for dark theme
class DarkThemeFactory extends UIFactory {
  createButton() {
    return {
      backgroundColor: "#1a1a1a",
      color: "#ffffff",
      border: "1px solid #333333"
    };
  }

  createInput() {
    return {
      backgroundColor: "#1a1a1a",
      color: "#ffffff",
      border: "1px solid #333333"
    };
  }
}

// Client code uses the abstract interface
function createLoginForm(factory) {
  const button = factory.createButton();
  const input = factory.createInput();

  return {
    button,
    input,
    render() {
      console.log("Button styles:", button);
      console.log("Input styles:", input);
    }
  };
}

// Usage
const lightForm = createLoginForm(new LightThemeFactory());
const darkForm = createLoginForm(new DarkThemeFactory());

lightForm.render();
// Button styles: { backgroundColor: '#ffffff', color: '#000000', ... }
darkForm.render();
// Button styles: { backgroundColor: '#1a1a1a', color: '#ffffff', ... }

The client code (createLoginForm) doesn’t know about the concrete factories. It just works with the UIFactory interface. When you need to support a new theme, create a new factory class that implements the same interface—you never need to change the client code.

This pattern is common in cross-platform UI frameworks, theming systems, and any situation where you need to create related objects that must work together.

When to Use Factories

Consider using factory functions when:

  1. Object creation involves logic: If creating an object requires validation, transformations, or conditional logic, a factory keeps that complexity contained.
  2. You need consistency: Factories ensure every object gets default properties and follows the same creation process.
  3. You want to avoid the new keyword: For those who prefer avoiding constructors, factories provide a cleaner syntax.
  4. Creating object families: When you need groups of related objects that must be used together, the abstract factory pattern organizes this cleanly.

Summary

The factory pattern gives you a flexible way to create objects in JavaScript:

  • Factory functions return new objects without requiring the new keyword
  • They provide a central place for creation logic, defaults, and validation
  • Configurable factories accept options to customize object creation
  • The abstract factory pattern creates families of related objects through a common interface

Understanding these patterns helps you write code that is easier to maintain and extend. When object creation involves complexity or you need to ensure consistency across instances, factories provide a clean solution.

See Also