Learn JavaScript factory functions and ES6 classes. Understand constructors, prototypes, private fields, inheritance, and when to use each pattern.
How do you create hundreds of similar objects without copy-pasting? How do game developers spawn thousands of enemies? How does JavaScript let you build blueprints for objects?
Copy
Ask AI
// Factory function — returns a new object each timefunction createPlayer(name) { return { name, health: 100, attack() { return `${this.name} attacks!` } }}// Class — a blueprint for creating objectsclass Enemy { constructor(name) { this.name = name this.health = 100 } attack() { return `${this.name} attacks!` }}// Both create objects the same wayconst player = createPlayer("Alice") // Factoryconst enemy = new Enemy("Goblin") // Classconsole.log(player.attack()) // "Alice attacks!"console.log(enemy.attack()) // "Goblin attacks!"
Factories and Classes are two patterns for creating objects efficiently. A factory function is a regular function that returns a new object. A class is a blueprint that uses the class keyword and the new operator. Both achieve the same goal, but they work differently and have different strengths. According to the 2023 State of JS survey, class syntax is now widely adopted, with the majority of JavaScript developers using classes regularly in their projects.
What you’ll learn in this guide:
How to create objects using factory functions
How constructor functions and the new keyword work
ES6 class syntax and what “syntactic sugar” means
Private fields (#) and how they differ from closures
Hand-crafting each item individually is slow, inconsistent, and doesn’t scale
Assembly lines (factories) take specifications and produce products efficiently
Blueprints/molds define the template once, then stamp out identical copies
JavaScript gives us the same options:
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────────────┐│ THREE WAYS TO CREATE OBJECTS │├─────────────────────────────────────────────────────────────────────────┤│ ││ MANUAL CREATION Like hand-carving each chess piece ││ ─────────────── Tedious, error-prone, inconsistent ││ const obj = { ... } ││ ││ ───────────────────────────────────────────────────────────────────── ││ ││ FACTORY FUNCTION Like an assembly line ││ ──────────────── Put in specs → Get product ││ Flexible, no special keywords ││ createPlayer("Alice") ││ │ ││ ▼ ││ ┌─────────────┐ ││ │ Player │ ← New object returned ││ │ {name...} │ ││ └─────────────┘ ││ ││ ───────────────────────────────────────────────────────────────────── ││ ││ CLASS / CONSTRUCTOR Like a blueprint or mold ││ ─────────────────── Define template → Stamp out copies ││ Uses `new`, supports `instanceof` ││ new Player("Alice") ││ │ ││ ▼ ││ ┌─────────────┐ ││ │ Player │ ← Instance created from blueprint ││ │ {name...} │ ││ └─────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────┘
Both factories and classes solve the same problem. They just do it differently. Let’s explore each approach.
A factory function is a regular JavaScript function that creates and returns a new object each time it’s called. Unlike constructors or classes, factory functions don’t require the new keyword. They can use this in returned methods (like simple objects do), or use closures to avoid this entirely, giving you flexibility that classes don’t offer. As Douglas Crockford documented in JavaScript: The Good Parts, factory functions leverage JavaScript’s prototypal nature more directly than class-based patterns.
A powerful feature of factory functions is creating truly private variables using closures:
Copy
Ask AI
function createBankAccount(ownerName, initialBalance = 0) { // Private variables — NOT accessible from outside let balance = initialBalance; const transactionHistory = []; // Private function function recordTransaction(type, amount) { transactionHistory.push({ type, amount, balance, date: new Date().toISOString() }); } // Initialize recordTransaction("opening", initialBalance); // Return public interface return { owner: ownerName, deposit(amount) { if (amount <= 0) { throw new Error("Deposit amount must be positive"); } balance += amount; recordTransaction("deposit", amount); return `Deposited $${amount}. New balance: $${balance}`; }, withdraw(amount) { if (amount <= 0) { throw new Error("Withdrawal amount must be positive"); } if (amount > balance) { throw new Error("Insufficient funds"); } balance -= amount; recordTransaction("withdrawal", amount); return `Withdrew $${amount}. New balance: $${balance}`; }, getBalance() { return balance; }, getStatement() { return transactionHistory.map(t => `${t.date}: ${t.type} $${t.amount} (Balance: $${t.balance})` ).join('\n'); } };}const account = createBankAccount("Alice", 1000);console.log(account.deposit(500)); // "Deposited $500. New balance: $1500"console.log(account.withdraw(200)); // "Withdrew $200. New balance: $1300"console.log(account.getBalance()); // 1300// Trying to access private variables — FAILS!console.log(account.balance); // undefinedconsole.log(account.transactionHistory); // undefined// Can't cheat!account.balance = 1000000; // Does nothing usefulconsole.log(account.getBalance()); // Still 1300
Why is this private? The variables balance and transactionHistory exist only inside the factory function. The returned object’s methods can access them through closure, but nothing outside can. This is true encapsulation!
Factory functions with closures provide real privacy. Variables inside the factory can’t be accessed or modified from outside, not even through hacks or reflection.
You don't need instanceof checks
Factory-created objects are plain objects. They don’t have a special prototype chain, so instanceof won’t work. If you need to check object types, use classes instead.
You want flexibility over structure
Factories can return different types of objects, partially constructed objects, or even primitives. Classes always return instances of that class.
You prefer functional programming
Factory functions fit well with functional programming patterns. They’re just functions that return data.
A constructor function is a regular JavaScript function designed to be called with the new keyword. When invoked with new, it creates a new object, binds this to that object, and returns it automatically. Constructor names conventionally start with a capital letter to distinguish them from regular functions. This was the standard way to create objects before ES6 classes.
// Convention: Constructor names start with a capital letterfunction Player(name) { // 'this' refers to the new object being created this.name = name; this.health = 100; this.level = 1; this.attack = function() { return `${this.name} attacks for ${10 + this.level * 2} damage!`; };}// Create instances with 'new'const alice = new Player("Alice");const bob = new Player("Bob");console.log(alice.name); // "Alice"console.log(bob.attack()); // "Bob attacks for 12 damage!"console.log(alice instanceof Player); // true
When you call new Player("Alice"), JavaScript performs 4 steps:
1
Create a new empty object
JavaScript creates a fresh object: const obj = {}
2
Link the prototype
Sets obj.[[Prototype]] to Constructor.prototype, establishing the prototype chain
3
Execute the constructor
Runs the constructor with this bound to the new object
4
Return the object
Returns obj automatically (unless the constructor explicitly returns a different non-null object; primitive return values are ignored)
Want to dive deeper? For a detailed explanation of how new works under the hood, including how to simulate it yourself, see Object Creation & Prototypes.
There’s a problem with our constructor: each instance gets its own copy of methods:
Copy
Ask AI
function Player(name) { this.name = name; this.health = 100; // BAD: Every player gets their own copy of this function this.attack = function() { return `${this.name} attacks!`; };}const p1 = new Player("Alice");const p2 = new Player("Bob");// These are different functions!console.log(p1.attack === p2.attack); // false// 1000 players = 1000 copies of attack function = wasted memory!
function Player(name) { this.name = name; this.health = 100; // Don't put methods here!}// Add methods to the prototype — shared by all instancesPlayer.prototype.attack = function() { return `${this.name} attacks!`;};Player.prototype.takeDamage = function(amount) { this.health -= amount; return `${this.name} has ${this.health} health.`;};const p1 = new Player("Alice");const p2 = new Player("Bob");// Now they share the same function!console.log(p1.attack === p2.attack); // true// 1000 players = 1 copy of attack function = efficient!
function Player(name) { this.name = name; this.health = 100;}// Oops! Forgot 'new'const alice = Player("Alice");console.log(alice); // undefined (function returned nothing)console.log(name); // "Alice" — LEAKED to global scope!console.log(health); // 100 — ALSO leaked!// In strict mode, this would throw an error instead// 'use strict';// Player("Alice"); // TypeError: Cannot set property 'name' of undefined
Always use new with constructor functions! Without it, this refers to the global object (or undefined in strict mode), causing bugs that are hard to track down.
An ES6 class is JavaScript’s modern syntax for creating constructor functions and prototypes. Introduced in ECMAScript 2015, classes provide a cleaner, more familiar syntax for object-oriented programming while working exactly the same as constructor functions under the hood. They’re often called “syntactic sugar.” Classes use the class keyword and require the new operator to create instances.
When working with factories and classes, there are several common pitfalls that trip up developers. Let’s look at the most frequent mistakes and how to avoid them.
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────────────┐│ THE 3 MOST COMMON MISTAKES │├─────────────────────────────────────────────────────────────────────────┤│ ││ 1. FORGETTING `new` WITH CONSTRUCTORS ││ Pollutes global scope or crashes in strict mode ││ ││ 2. FORGETTING `super()` IN DERIVED CLASSES ││ Must call super() before using `this` ││ ││ 3. CONFUSING `_private` WITH TRULY PRIVATE ││ Underscore is just a convention, not enforcement ││ │└─────────────────────────────────────────────────────────────────────────┘
Mistake 1: Forgetting new with Constructor Functions
Copy
Ask AI
// ❌ WRONG - Forgot 'new', 'this' becomes global objectfunction Player(name) { this.name = name; this.health = 100;}const alice = Player("Alice"); // Missing 'new'!console.log(alice); // undefinedconsole.log(globalThis.name); // "Alice" - leaked to global!console.log(globalThis.health); // 100 - also leaked!// ✓ CORRECT - Always use 'new' with constructorsconst bob = new Player("Bob");console.log(bob.name); // "Bob"console.log(bob.health); // 100
Pro tip: Use ES6 classes instead of constructor functions — they throw an error if you forget new:
Copy
Ask AI
class Player { constructor(name) { this.name = name; }}const alice = Player("Alice"); // TypeError: Class constructor Player cannot be invoked without 'new'
Mistake 4: Using this Incorrectly in Factory Functions
Copy
Ask AI
// ❌ WRONG - 'this' in factory return object can cause issuesfunction createCounter() { return { count: 0, increment() { this.count++; // 'this' depends on how the method is called } };}const counter = createCounter();counter.increment(); // Worksconsole.log(counter.count); // 1const increment = counter.increment;increment(); // 'this' is undefined or global!console.log(counter.count); // Still 1 - didn't work!// ✓ CORRECT - Use closure to avoid 'this' issuesfunction createSafeCounter() { let count = 0; // Closure variable return { increment() { count++; // No 'this' needed }, getCount() { return count; } };}const safeCounter = createSafeCounter();const safeIncrement = safeCounter.increment;safeIncrement(); // Works even when extracted!console.log(safeCounter.getCount()); // 1
The this Trap: When you extract a method from an object and call it standalone, this is no longer bound to the original object. Factory functions that use closures instead of this avoid this problem entirely.
Arrow Function Class Fields: In classes, you can use arrow functions as class fields to auto-bind this:
Copy
Ask AI
class Button { count = 0; // Arrow function automatically binds 'this' to the instance handleClick = () => { this.count++; console.log(`Clicked ${this.count} times`); };}const button = new Button();const handler = button.handleClick;handler(); // Works! 'this' is still bound to button
This is an alternative to manually binding methods with .bind(this) in the constructor.
What's the difference between a factory function and a class?
Answer:
Aspect
Factory Function
ES6 Class
Syntax
Regular function returning object
class keyword
new keyword
Not required
Required
instanceof
Doesn’t work
Works
Privacy
Closures (truly private)
Private fields # (truly private)
Memory
Each instance has own methods
Methods shared via prototype
this binding
Can avoid this entirely
Must use this
Copy
Ask AI
// Factory - just a functionfunction createUser(name) { return { name, greet() { return `Hi, ${name}!` } }}// Class - a blueprintclass User { constructor(name) { this.name = name } greet() { return `Hi, ${this.name}!` }}const u1 = createUser("Alice") // No 'new'const u2 = new User("Bob") // Requires 'new'
Best answer: Explain both syntax differences AND when to use each.
What does the new keyword do under the hood?
Answer:new performs 4 steps:
Creates a new empty object {}
Links its prototype to Constructor.prototype
Executes the constructor with this bound to the new object
Returns the object (unless constructor returns a different object)
Copy
Ask AI
// This is essentially what 'new' does:function myNew(Constructor, ...args) { const obj = Object.create(Constructor.prototype) const result = Constructor.apply(obj, args) return (typeof result === 'object' && result !== null) ? result : obj}
Best answer: Mention all 4 steps and show the simulation code.
How do you achieve true privacy in JavaScript?
Answer:Two ways to achieve true privacy:1. Private Fields (#) in Classes:
Copy
Ask AI
class BankAccount { #balance = 0 deposit(amt) { this.#balance += amt } getBalance() { return this.#balance }}// account.#balance → SyntaxError!
2. Closures in Factory Functions:
Copy
Ask AI
function createBankAccount() { let balance = 0 return { deposit(amt) { balance += amt }, getBalance() { return balance } }}// account.balance → undefined
Not truly private: The _underscore convention is just a naming hint. Those properties are fully accessible.Best answer: Distinguish between the _underscore convention (not private) and the two truly private approaches.
When would you use composition over inheritance?
Answer:Use composition when:
You need to mix behaviors from multiple sources (a flying fish, a swimming bird)
The “is-a” relationship doesn’t make sense
You want loose coupling between components
You need flexibility to change behaviors at runtime
Use inheritance when:
There’s a clear “is-a” hierarchy (Dog is an Animal)
You need instanceof checks
You want to share implementation, not just interface
Copy
Ask AI
// Inheritance problem: What about a penguin that can't fly?class Bird { fly() {} }class Penguin extends Bird { fly() { throw Error("Can't fly!") } } // Awkward!// Composition solution: Mix behaviorsconst canSwim = (state) => ({ swim() { /*...*/ } })const canWalk = (state) => ({ walk() { /*...*/ } })function createPenguin(name) { const state = { name } return { ...canSwim(state), ...canWalk(state) } // No fly!}
Best answer: Give the “Gorilla-Banana” problem example and show composition code.
Misconception: 'Classes in JavaScript work like classes in Java or C#'
Reality: JavaScript classes are syntactic sugar over prototypes. Under the hood, they still use prototype-based inheritance, not classical inheritance.
Copy
Ask AI
class Player { constructor(name) { this.name = name } attack() { return `${this.name} attacks!` }}// Classes ARE functions!console.log(typeof Player) // "function"// Methods are on the prototype, not the instanceconsole.log(Player.prototype.attack) // [Function: attack]
This is why JavaScript has quirks like this binding issues that don’t exist in true class-based languages.
Misconception: 'Factory functions are less powerful than classes'
Reality: Factory functions can do everything classes can, plus more:
True privacy via closures (before # existed)
No this binding issues when using closures
Return different types based on input
No new keyword to forget
Copy
Ask AI
// Factory can return different types!function createShape(type) { if (type === 'circle') return { radius: 10, area() { /*...*/ } } if (type === 'square') return { side: 10, area() { /*...*/ } }}// Classes always return instances of that class
The trade-off is memory efficiency (classes share methods via prototype).
Misconception: 'Private fields (#) and _underscore are the same thing'
Reality: They’re completely different:
Aspect
_underscore
#privateField
Accessibility
Fully public
Truly private
Convention only?
Yes
No, enforced
Error on access
No error
SyntaxError
Copy
Ask AI
class Account { _balance = 100 // Accessible! Just a convention #pin = 1234 // Truly private}const acc = new Account()console.log(acc._balance) // 100 — works!// console.log(acc.#pin) // SyntaxError!
Misconception: 'You should always use classes because they're the modern way'
Reality: Classes were added in ES6 (2015), but that doesn’t mean they’re always better. The JavaScript community has moved toward functions in many cases:
React: Moved from class components to function components with hooks
Functional programming: Favors factory functions and composition
Simplicity: Factory functions have fewer footguns (this, new)
Use classes when: You need instanceof, clear hierarchies, or OOP familiarity.Use factories when: You need composition, true privacy, or functional style.
In constructor: Calls the parent’s constructor (super(...))
In methods: Accesses parent’s methods (super.method())
Copy
Ask AI
class Animal { constructor(name) { this.name = name; } speak() { return `${this.name} makes a sound.`; }}class Dog extends Animal { constructor(name, breed) { // MUST call super() before using 'this' in derived class super(name); // Calls Animal's constructor this.breed = breed; } speak() { // Call parent method and add to it const parentSays = super.speak(); return `${parentSays} Specifically: Woof!`; }}const rex = new Dog("Rex", "German Shepherd");console.log(rex.speak());// "Rex makes a sound. Specifically: Woof!"
In a derived class constructor, you MUST call super() before using this. JavaScript needs to initialize the parent part of the object first.
Copy
Ask AI
class Child extends Parent { constructor(name) { // this.name = name; // ERROR! Can't use 'this' yet super(); // Must call super first this.name = name; // Now 'this' is available }}
Inheritance can become problematic with deep hierarchies:
Copy
Ask AI
// The "Gorilla-Banana Problem"class Animal { }class Mammal extends Animal { }class Primate extends Mammal { }class Ape extends Primate { }class Gorilla extends Ape { }// You wanted a banana, but you got the whole jungle!// - Deep chains are hard to understand// - Changes to parent classes can break children// - Tight coupling between classes
┌─────────────────────────────────────────────────────────────────────┐│ WHICH SHOULD I USE? ││ ││ Do you need instanceof checks? ││ YES ──► Use Class ││ NO ──▼ ││ ││ Do you need a clear inheritance hierarchy? ││ YES ──► Use Class with extends ││ NO ──▼ ││ ││ Do you need to mix multiple behaviors? ││ YES ──► Use Factory with composition ││ NO ──▼ ││ ││ Do you need truly private data? ││ YES ──► Either works (Factory closures OR Class with #) ││ NO ──▼ ││ ││ Is your team familiar with OOP? ││ YES ──► Use Class (more familiar syntax) ││ NO ──► Use Factory (simpler mental model) │└─────────────────────────────────────────────────────────────────────┘
Question 4: What does super() do and when must you call it?
Answer:super() calls the parent class’s constructor. You must call it in a derived class constructor before using this.
Copy
Ask AI
class Animal { constructor(name) { this.name = name; }}class Dog extends Animal { constructor(name, breed) { // this.breed = breed; // ERROR! Can't use 'this' yet super(name); // Call parent constructor first this.breed = breed; // Now 'this' is available }}
super.method() calls a parent’s method from within an overriding method.
Question 5: When would you use composition over inheritance?
Answer:Use composition when:
You need to mix behaviors from multiple sources (a flying fish, a swimming bird)
The “is-a” relationship doesn’t make sense
You want loose coupling between components
You need flexibility to change behaviors at runtime
Use inheritance when:
There’s a clear “is-a” hierarchy (Dog is an Animal)
You need instanceof checks
You want to share implementation, not just interface
Rule of thumb: “Favor composition over inheritance” — composition is more flexible.
Question 6: Why are ES6 classes called 'syntactic sugar'?
Answer:Classes are called “syntactic sugar” because they don’t add new functionality — they just provide a cleaner syntax for constructor functions and prototypes.
Copy
Ask AI
// This class...class Player { constructor(name) { this.name = name; } attack() { return `${this.name} attacks!`; }}// ...is equivalent to:function Player(name) { this.name = name; }Player.prototype.attack = function() { return `${this.name} attacks!`; };// Both create the same result:typeof Player === 'function' // true for both
The class syntax makes the code easier to read and write, but under the hood, JavaScript is still using prototypes.
What is the difference between factory functions and classes in JavaScript?
Factory functions are regular functions that return new objects. Classes are syntactic sugar over constructor functions and prototypes, using the class keyword and new operator. Factories offer simpler composition and true privacy through closures, while classes provide a familiar OOP syntax and share methods efficiently via the prototype chain.
Are JavaScript classes real classes like in Java or C++?
No. As stated in the ECMAScript specification, JavaScript classes are primarily syntactic sugar over the existing prototype-based inheritance model. Under the hood, a class declaration creates a constructor function with methods on its prototype. Unlike classical OOP languages, JavaScript does not have true class-based inheritance — it uses prototypal delegation.
What are private fields in JavaScript classes?
Private fields, prefixed with #, are truly private properties that cannot be accessed outside the class body. They were standardized in ECMAScript 2022 and are enforced by the JavaScript engine at the language level. Unlike the underscore convention (_name), private fields throw a SyntaxError if accessed externally.
When should I use a factory function instead of a class?
Use factories when you need true data privacy through closures, want to compose objects from multiple sources, or need to return different object types conditionally. Use classes when you want prototype-based method sharing, need instanceof checks, or work with frameworks that expect class syntax. According to the 2023 State of JS survey, class syntax is widely adopted, but factory patterns remain popular in functional-style codebases.
What does the new keyword do under the hood?
The new keyword performs four steps: it creates a new empty object, links that object’s prototype to the constructor’s prototype property, executes the constructor with this bound to the new object, and returns the object (unless the constructor explicitly returns a different object). This is the same for both constructor functions and classes.