Michael Ouroumis logoichael Ouroumis

Factory Functions vs Constructor Functions vs Classes

Abstract tech background with a turquoise-to-teal gradient and subtle circuit lines, overlaid with the title "Factory Functions vs Constructor Functions vs Classes" in bold white text.

TL;DR:

  • Factory functions are plain functions that return new objects—flexible and don’t require new.
  • Constructor functions use new to build objects with shared prototypes—classic ES5 pattern.
  • ES6 classes are syntactic sugar over constructor/prototype patterns—clearer syntax, built-in inheritance.

Use factory functions when you want privacy and flexibility, constructor functions in legacy/ES5 codebases, and ES6 classes for clear inheritance and encapsulation.


1. Factory Functions

Factory functions are simple functions that create and return new objects. They let you encapsulate private data via closures, and you don’t have to worry about using new.

function createUser(name, age) { // Private helper let secret = Math.random() return { name, age, getSecret() { return secret }, } } const alice = createUser('Alice', 30) console.log(alice.name) // → "Alice" console.log(alice.getSecret()) // → some random number
  • No new keyword required.
  • Private state via closed-over variables.
  • Flexibility: return different shapes of objects based on parameters.

2. Constructor Functions

Constructor functions are regular functions intended to be called with new. Under the hood, they create a fresh object, bind this, and return it (unless another object is explicitly returned).

function User(name, age) { this.name = name this.age = age } User.prototype.greet = function () { console.log(`Hi, I'm ${this.name}`) } const bob = new User('Bob', 25) bob.greet() // → "Hi, I'm Bob"
  • Use new User(...) to instantiate.
  • Shared methods via User.prototype.
  • Error-prone if you forget new: this will be undefined (in strict mode) or global.

3. ES6 Classes

Classes in ES6 provide a clearer, more concise syntax for constructor functions and inheritance, but they’re just syntactic sugar over prototypes.

class User { #secret // private field constructor(name, age) { this.name = name this.age = age this.#secret = Math.random() } getSecret() { return this.#secret } greet() { console.log(`Hello, ${this.name}`) } } const carol = new User('Carol', 28) carol.greet() // → "Hello, Carol" console.log(carol.getSecret()) // → some random number
  • class keyword with constructor and methods.
  • Private fields via #.
  • extends for inheritance: class Admin extends User { … }.

4. Visualizing the Differences

Factory: createUser() ──► returns new object literal (closure)
Constructor:   new User()    ──► Object ↔ User.prototype
Class:         new User()    ──► Object ↔ User.prototype (ES6 syntax)
  • Factory returns distinct objects each call.
  • Constructors & classes share methods on prototype.
  • Classes bundle constructor + prototype methods in one declaration.

5. Pitfalls & Best Practices

PatternPitfallTip
FactoryAll instances carry own method copiesExtract common behavior into shared helpers
ConstructorForgetting new leads to bugsUse linters or new.target checks
ClassesOverusing inheritance can create tight couplingFavor composition over deep class hierarchies

6. When to Use Which

  • Factory Functions:

    • You need private state or variable return shapes.
    • You prefer composition over inheritance.
  • Constructor Functions:

    • Working in ES5 environments or incremental codebases.
    • You want prototype-based sharing without ES6 syntax.
  • ES6 Classes:

    • You need clear inheritance chains.
    • You want built-in support for private fields and static methods.

By understanding these three patterns, you’ll choose the right tool for creating objects—balancing simplicity, performance, and maintainability in your JavaScript code.

Enjoyed this post? Share it: