Factory vs Constructor Functions: When Prototypes Don't Behave as Expected
Have you ever found yourself scratching your head while debugging JavaScript prototype behavior? I recently encountered a fascinating quirk that highlights an important distinction between factory functions and constructor functions. Let's explore why the following code behaves the way it does: function purr() { console.log(`${this.name} is purring`) } // FACTORY FUNCTION function createCat(name, breed) { return { name, breed, greet() { console.log(`Hello, my name is ${this.name}`) } }; } createCat.prototype.purr = purr const oreo = createCat("Oreo", "Persian") oreo.greet() // Hello, my name is Oreo // oreo.purr() // TypeError: oreo.purr is not a function //CONSTRUCTOR FUNCTION function Cat(name, breed){ this.name = name this.breed = breed this.greet = function(){ console.log(`Hello, my name is ${this.name}`) } } Cat.prototype.purr = purr const frosty = new Cat("Frosty", "Tabby") frosty.greet() // Hello, my name is Frosty frosty.purr() // Frosty is purring The Mystery: Why Can't Oreo Purr? Oreo and Frosty are both cats who can greet us, but only Frosty can purr! When we try to make Oreo purr, JavaScript throws a TypeError. What's going on here? Understanding the Root Cause The difference comes down to how prototypal inheritance works for objects created using different patterns: Factory Functions: Plain Objects with No Prototype Connection When we use a factory function like createCat(), it simply returns a plain object literal. This object's prototype is linked to Object.prototype, not to createCat.prototype. In our example: We added purr() to createCat.prototype But oreo doesn't inherit from createCat.prototype at all! The prototype chain for oreo looks like: oreo → Object.prototype → null Constructor Functions: The Magic of new When we use the new keyword with a constructor function like Cat(), something special happens: A new object is created This object's prototype is set to Cat.prototype The constructor function is executed with this bound to the new object The new object is returned (unless the constructor returns another object) So for frosty: We added purr() to Cat.prototype frosty inherits from Cat.prototype The prototype chain looks like: frosty → Cat.prototype → Object.prototype → null How to Fix Oreo's Inability to Purr If we want objects created by factory functions to share methods, we have a few options: Option 1: Include the method directly in each object function createCat(name, breed) { return { name, breed, greet() { console.log(`Hello, my name is ${this.name}`) }, purr // Add purr method to each object }; } Option 2: Use Object.create for proper prototype inheritance const catMethods = { greet() { console.log(`Hello, my name is ${this.name}`) }, purr() { console.log(`${this.name} is purring`) } }; function createCat(name, breed) { const cat = Object.create(catMethods); cat.name = name; cat.breed = breed; return cat; } Option 3: Use classes (which are syntactic sugar for constructor functions) class Cat { constructor(name, breed) { this.name = name; this.breed = breed; } greet() { console.log(`Hello, my name is ${this.name}`) } purr() { console.log(`${this.name} is purring`) } } Memory Efficiency Considerations One advantage of the prototype approach is memory efficiency. When we add methods to prototypes rather than directly to each object: With constructor functions, methods like purr() exist once in memory (on the prototype) With our original factory function, greet() is recreated for each object, wasting memory The Big Takeaways Factory functions create objects from scratch with no inherent prototype linking Constructor functions with new automatically link the created object to the constructor's prototype If you're using factory functions and want shared methods, you need to: Include them in each object (memory inefficient) Use Object.create() to establish proper inheritance Or switch to classes/constructors Which Should You Use? Both patterns have their place in modern JavaScript: Factory functions are great for creating objects with private data using closures Constructor functions and classes provide a cleaner inheritance model for shared behavior The key is understanding how JavaScript's prototype system works under the hood, so you don't get caught by surprising behaviors like our poor Oreo who couldn't purr! What's your preferred way of creating objects in JavaScript? Have you encountered other prototype quirks? Share in the comments below! Follow me for more JavaScript insights and explanations of those weird behavi

Have you ever found yourself scratching your head while debugging JavaScript prototype behavior? I recently encountered a fascinating quirk that highlights an important distinction between factory functions and constructor functions. Let's explore why the following code behaves the way it does:
function purr() {
console.log(`${this.name} is purring`)
}
// FACTORY FUNCTION
function createCat(name, breed) {
return {
name,
breed,
greet() {
console.log(`Hello, my name is ${this.name}`)
}
};
}
createCat.prototype.purr = purr
const oreo = createCat("Oreo", "Persian")
oreo.greet() // Hello, my name is Oreo
// oreo.purr() // TypeError: oreo.purr is not a function
//CONSTRUCTOR FUNCTION
function Cat(name, breed){
this.name = name
this.breed = breed
this.greet = function(){
console.log(`Hello, my name is ${this.name}`)
}
}
Cat.prototype.purr = purr
const frosty = new Cat("Frosty", "Tabby")
frosty.greet() // Hello, my name is Frosty
frosty.purr() // Frosty is purring
The Mystery: Why Can't Oreo Purr?
Oreo and Frosty are both cats who can greet us, but only Frosty can purr! When we try to make Oreo purr, JavaScript throws a TypeError
. What's going on here?
Understanding the Root Cause
The difference comes down to how prototypal inheritance works for objects created using different patterns:
Factory Functions: Plain Objects with No Prototype Connection
When we use a factory function like createCat()
, it simply returns a plain object literal. This object's prototype is linked to Object.prototype
, not to createCat.prototype
.
In our example:
- We added
purr()
tocreateCat.prototype
- But
oreo
doesn't inherit fromcreateCat.prototype
at all! - The prototype chain for
oreo
looks like:oreo
→Object.prototype
→null
Constructor Functions: The Magic of new
When we use the new
keyword with a constructor function like Cat()
, something special happens:
- A new object is created
- This object's prototype is set to
Cat.prototype
- The constructor function is executed with
this
bound to the new object - The new object is returned (unless the constructor returns another object)
So for frosty
:
- We added
purr()
toCat.prototype
-
frosty
inherits fromCat.prototype
- The prototype chain looks like:
frosty
→Cat.prototype
→Object.prototype
→null
How to Fix Oreo's Inability to Purr
If we want objects created by factory functions to share methods, we have a few options:
Option 1: Include the method directly in each object
function createCat(name, breed) {
return {
name,
breed,
greet() {
console.log(`Hello, my name is ${this.name}`)
},
purr // Add purr method to each object
};
}
Option 2: Use Object.create for proper prototype inheritance
const catMethods = {
greet() {
console.log(`Hello, my name is ${this.name}`)
},
purr() {
console.log(`${this.name} is purring`)
}
};
function createCat(name, breed) {
const cat = Object.create(catMethods);
cat.name = name;
cat.breed = breed;
return cat;
}
Option 3: Use classes (which are syntactic sugar for constructor functions)
class Cat {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
greet() {
console.log(`Hello, my name is ${this.name}`)
}
purr() {
console.log(`${this.name} is purring`)
}
}
Memory Efficiency Considerations
One advantage of the prototype approach is memory efficiency. When we add methods to prototypes rather than directly to each object:
- With constructor functions, methods like
purr()
exist once in memory (on the prototype) - With our original factory function,
greet()
is recreated for each object, wasting memory
The Big Takeaways
- Factory functions create objects from scratch with no inherent prototype linking
-
Constructor functions with
new
automatically link the created object to the constructor's prototype - If you're using factory functions and want shared methods, you need to:
- Include them in each object (memory inefficient)
- Use
Object.create()
to establish proper inheritance - Or switch to classes/constructors
Which Should You Use?
Both patterns have their place in modern JavaScript:
- Factory functions are great for creating objects with private data using closures
- Constructor functions and classes provide a cleaner inheritance model for shared behavior
The key is understanding how JavaScript's prototype system works under the hood, so you don't get caught by surprising behaviors like our poor Oreo who couldn't purr!
What's your preferred way of creating objects in JavaScript? Have you encountered other prototype quirks? Share in the comments below!
Follow me for more JavaScript insights and explanations of those weird behaviors that make you go "wait, what?!"