The JavaScript Factory Pattern: Complete Guide

The factory pattern is a creational design pattern that lets you create objects without exposing the instantiation logic. Learn how it works, when to use it, and how it simplifies object creation in JavaScript.

7 min read

A factory function is a function that creates and returns a new object. Unlike constructors called with new, a factory function is just a regular function call. You pass in arguments, and it hands you back a fully formed object. The caller never sees how the object was built.

The name comes from a real factory: you place an order (call the function with parameters), and the factory delivers a finished product (the object). You do not need to know the assembly line details.

Why Use a Factory Instead of a Constructor

Here is the problem factories solve. Say you are building objects that need validation, defaults, or conditional setup before they are ready to use:

javascriptjavascript
function createUser(name, role) {
  // Validate inputs
  if (!name || typeof name !== "string") {
    throw new Error("User must have a valid name");
  }
 
  // Set defaults and derived values
  const createdAt = new Date().toISOString();
  const isAdmin = role === "admin";
 
  return {
    name,
    role: role || "viewer",
    isAdmin,
    createdAt,
    describe() {
      return `${name} (${this.role}) -- created ${this.createdAt}`;
    }
  };
}
 
const alice = createUser("Alice", "admin");
console.log(alice.describe());
// Alice (admin) -- created 2026-07-15T...

The caller writes createUser("Alice", "admin") and gets back a ready-to-use object. All the validation, default logic, and derived properties live inside the factory, not scattered across every place that creates a user.

The Core Idea: Return an Object

A factory function always returns an object. What happens inside the function is up to you. This is the simplest possible factory:

javascriptjavascript
function createPoint(x, y) {
  return { x, y };
}
 
const p = createPoint(10, 20);
console.log(p); // { x: 10, y: 20 }

It looks trivial, and it is. The power comes from what you can add between the parameters and the return statement: validation, computed properties, private data, or conditional logic.

Returning Different Object Types

A factory can return completely different objects depending on its inputs. This is where factories shine compared to constructors:

javascriptjavascript
function createNotifier(type) {
  if (type === "email") {
    return {
      type: "email",
      send(message) {
        console.log(`Email sent: ${message}`);
      }
    };
  }
 
  if (type === "sms") {
    return {
      type: "sms",
      send(message) {
        console.log(`SMS sent to phone: ${message}`);
      }
    };
  }
 
  return {
    type: "console",
    send(message) {
      console.log(`Log: ${message}`);
    }
  };
}
 
const emailNotifier = createNotifier("email");
emailNotifier.send("Your order shipped");
// Email sent: Your order shipped
 
const smsNotifier = createNotifier("sms");
smsNotifier.send("Your code is 1234");
// SMS sent to phone: Your code is 1234

One function, three different object shapes. The caller only needs to know the type string. This is the strategy behind the factory: centralize creation logic so callers stay simple.

Private State with Closures

A factory function can use closures to create truly private data. Properties on the returned object are public. Variables inside the factory function are private:

javascriptjavascript
function createCounter(start = 0) {
  let count = start; // Private -- not on the returned object
 
  return {
    increment() {
      count += 1;
      return count;
    },
    decrement() {
      count -= 1;
      return count;
    },
    getCount() {
      return count;
    }
  };
}
 
const counter = createCounter(5);
console.log(counter.getCount()); // 5
console.log(counter.increment()); // 6
console.log(counter.increment()); // 7
console.log(counter.count);       // undefined -- private

Nobody can reach count from outside. The only way to read or change it is through the methods you returned. This is encapsulation without classes, this, or private keywords.

The factory flow makes the public-private boundary clear:

Factory function with private closure state

The returned methods form the public API. They are the only bridge to the private count variable. External code that tries counter.count gets undefined because count was never placed on the object.

Factory vs Constructor vs Class

JavaScript gives you several ways to create objects. Here is how they compare:

ApproachSyntaxnew required?Prototype chainPrivate data
Factory functionfn() returns {}NoNo (plain object)Via closures
Constructornew Fn()YesYesNo (without #)
Classnew Class()YesYesWith #private fields

Factories are the simplest option when you do not need prototype inheritance. Classes are better when you need instanceof checks or shared methods on the prototype. Constructors are largely replaced by classes in modern JavaScript.

When to Use the Factory Pattern

Use a factory function when:

  • Object creation involves validation, defaults, or conditional logic you want to keep in one place.
  • You need to return different object shapes from the same conceptual "create" call.
  • You want private data without class syntax or WeakMap.
  • You are building a library and want to hide internal object structure from consumers.
  • You want to avoid forcing callers to remember the new keyword.

Skip the factory pattern when:

  • The object is simple enough that { x, y } is clearer than createPoint(x, y).
  • You need prototype-based inheritance or instanceof checks.
  • You are working in a codebase that consistently uses classes for consistency.

Common Mistake: Forgetting That Factories Return Plain Objects

A factory returns a plain object, not an instance of a class. This means:

javascriptjavascript
function createDog(name) {
  return { name, bark() { return `${name} says woof`; } };
}
 
const dog = createDog("Rex");
console.log(dog instanceof createDog); // false
console.log(dog.constructor);          // Object

There is no createDog prototype. If your code relies on instanceof or constructor checks, a factory is the wrong tool. Use a JavaScript class instead.

Factory in Practice: Form Field Builder

Here is a realistic example. You are building a form system where each field type needs different validation and rendering logic:

javascriptjavascript
function createField(config) {
  const { type, name, label, required } = config;
 
  const shared = { type, name, label, required };
 
  if (type === "text") {
    return {
      ...shared,
      validate(value) {
        if (required && !value) return `${label} is required`;
        return null;
      }
    };
  }
 
  if (type === "email") {
    return {
      ...shared,
      validate(value) {
        if (required && !value) return `${label} is required`;
        if (value && !value.includes("@")) return `${label} must be a valid email`;
        return null;
      }
    };
  }
 
  if (type === "number") {
    return {
      ...shared,
      min: config.min,
      max: config.max,
      validate(value) {
        if (required && value === "") return `${label} is required`;
        if (value !== "" && isNaN(Number(value))) return `${label} must be a number`;
        return null;
      }
    };
  }
 
  throw new Error(`Unknown field type: ${type}`);
}
 
const emailField = createField({
  type: "email",
  name: "userEmail",
  label: "Email address",
  required: true
});
 
console.log(emailField.validate(""));        // Email address is required
console.log(emailField.validate("bad"));     // Email address must be a valid email
console.log(emailField.validate("a@b.com")); // null (valid)

Each field type gets its own validation logic, but the caller just calls createField(config). Adding a new field type means adding another if block inside the factory. Nothing outside the factory changes.

This pattern pairs well with the strategy pattern when you want to swap behaviors at runtime rather than at creation time.

Rune AI

Rune AI

Key Insights

  • A factory function is a function that returns a new object without using the new keyword.
  • Factories hide object creation complexity behind a simple function call.
  • You can return different object types from the same factory based on arguments.
  • Factories work well with closures to create private state.
  • Use factories when object creation logic is complex or conditional.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between a factory function and a constructor?

A factory function is a regular function that returns a new object. A constructor is called with the new keyword and returns an instance linked to a prototype. Factories give you more control over the returned object and do not require callers to remember the new keyword.

When should I use the factory pattern instead of classes?

Use the factory pattern when object creation requires conditional logic, when you need to return different object types from a single function, or when you want to hide complex setup from the caller.

Are factory functions slower than constructors?

In modern JavaScript engines, the performance difference is negligible for most use cases. The flexibility factories provide usually outweighs any micro-optimization concern.

Conclusion

The factory pattern is a simple but powerful way to centralize and control object creation. Use it when you have complex setup logic, need to return different object types based on conditions, or want to hide implementation details from the code that creates objects.The factory pattern is about moving object creation complexity behind a simple function call. Instead of every part of your code knowing how to build and configure an object, only the factory needs that knowledge. Start with a plain function that returns an object. Add validation inside it. Use closures for private state when you need it. Return different object shapes based on arguments when that is useful. The pattern earns its keep as soon as object creation stops being a one-liner.