OOP is probably one of the first things we learn as programmers — in school, from tutorials, or when brushing up for that promotion and diving into design patterns. We’re taught about classes as blueprints, and objects as their instances. It feels universal: Java, C++, Python… OOP everywhere.
So naturally, when we start writing JS, we expect the same rules to apply. Classes, objects, inheritance — the works.
But here’s the twist: JavaScript fakes OOP really well — but under the hood, it’s something very different.
Let’s peel back that facade and see the truth of OOP in JS, using the very common example of a Human.
The Example Problem
Usually when OOP is taught, we pick a real-life analogy to make sense of it. So let’s do the same here.
We want to represent a Human. A Human has a name
, an age
, can greet()
others and also grows old celebrating with happyBirthday()
.
But here’s the catch: let’s forget OOP exists for a moment. How would we build this the JavaScript way?
Creating a Human
Lets keep it stupid simple, and just create a Human.
let human1 = {
name: "Adam",
age: 25,
greet: function () {
console.log(`Hi, I’m ${this.name}, ${this.age} years old.`);
},
happyBirthday: function () {
this.age++;
console.log(`It’s my birthday! I’m now ${this.age}.`);
}
};
human1.greet(); // Hi, I’m Adam, 25 years old.
human1.happyBirthday(); // It’s my birthday! I’m now 26.
That’s it we have created our Human, but this gives us only one Human. If we want more, we’d have to copy-paste this whole object. Too much effort, so lets reduce this and make a Human Factory
Factory Pattern
function createHuman(name, age) {
return {
name,
age,
greet: function () {
console.log(`Hi, I’m ${this.name}, ${this.age} years old.`);
},
happyBirthday: function () {
this.age++;
console.log(`It’s my birthday! I’m now ${this.age}.`);
}
};
}
const human1 = createHuman("Bob", 30);
human1.greet(); // Hi, I’m Bob, 30 years old.
human1.happyBirthday(); // It’s my birthday! I’m now 31.
dusts hands off — now I can create as many Humans as I like. Pretty neat.
But wait… problem spotted! Every Human gets its own copy of greet
and happyBirthday
. If I make 1,000 Humans, that’s 1,000 duplicate functions sitting in memory. Yikes.
Surely JavaScript has some secret sauce for this.
The Secret Sauce Revealed
Turns out JS does has a Secret sauce, instead of cloning methods for every object, JavaScript quietly gives each object a hidden backstage link. Through this link, objects can borrow methods from a shared place — no duplication needed.
const humanMethods = {
greet: function () {
console.log(`Hi, I’m ${this.name}, ${this.age} years old.`);
},
happyBirthday: function () {
this.age++;
console.log(`It’s my birthday! I’m now ${this.age}.`);
}
};
function createHuman(name, age) {
let human = Object.create(humanMethods); // <-- the hidden link
human.name = name;
human.age = age;
return human;
}
const h1 = createHuman("Charlie", 22);
h1.greet(); // Hi, I’m Charlie, 22 years old.
h1.happyBirthday(); // It’s my birthday! I’m now 23.
This hidden link is nothing but a hidden property called __proto__
, which every JavaScript object has.
👉And remember, in JavaScript everything is an object (well… except a few primitives, but even they behave like objects when needed. And no — not class instance objects, just plain JS objects).
When JS runs and can’t find a property directly on your object, it doesn’t panic. Instead, it follows this hidden __proto__
trail, looking for the property in the linked object. That’s how greet
and happyBirthday
magically work without being duplicated.
Pretty neat, right?
Walks in the new
Approach
But wiring up these hidden links with Object.create
still feels a bit manual. Wouldn’t it be nice if JavaScript gave us a cleaner way to do it?
Turns out, functions themselves carry a secret power: they’re objects too, and each one has its own “backstage” property called prototype
. And when you combine that with the new
keyword… boom, you get a much cleaner way to wire things up.
function Human(name, age) {
this.name = name;
this.age = age;
}
Human.prototype.greet = function () {
console.log(`Hi, I’m ${this.name}, ${this.age} years old.`);
};
Human.prototype.happyBirthday = function () {
this.age++;
console.log(`It’s my birthday! I’m now ${this.age}.`);
};
const h1 = new Human("Dave", 40);
h1.greet(); // Hi, I’m Dave, 40 years old.
h1.happyBirthday(); // It’s my birthday! I’m now 41.
Much cleaner! Now the “hidden link” is managed automatically by new
.
*👉 Curious about how this hidden link (aka prototypes) really work under the hood?
💬 Drop a comment below if you’d like me to write a full breakdown of prototypes in my next blog.
-
Feels like a class, right? But don’t be fooled.
Human
is just a function, andnew Human()
creates a plain JS object linked toHuman.prototype
. Still no real classes yet.
The Final Facade - Classes
So far, we’ve gone from copy-pasting objects → factory functions → constructor functions with prototypes. Each step got us closer to something that looked like OOP, while still being powered by JavaScript’s hidden prototype magic.
And then ES6 came along and said:
“Hey devs, I see you’re faking classes anyway… why not make it look familiar?”
Enter class
.
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I’m ${this.name}, ${this.age} years old.`);
}
happyBirthday() {
this.age++;
console.log(`It’s my birthday! I’m now ${this.age}.`);
}
}
const h1 = new Human("Eve", 28);
h1.greet(); // Hi, I’m Eve, 28 years old.
h1.happyBirthday(); // It’s my birthday! I’m now 29.
Now it really looks like OOP. The same familiar feeling like with JAVA, C++, Python…
But here’s the kicker:
👉 class
in JavaScript doesn’t invent a new OOP system. It’s pure syntactic sugar.
Behind the curtain, that class Human
is still just a constructor function. Its methods? Still attached to Human.prototype
. The “hidden link” we talked about? Still doing the heavy lifting.
The only real difference is how nice it looks when you type it.
So yeah, JS gives us the full OOP costume — but don’t be fooled. The foundation is still prototypes, all the way down.
console.log(typeof Human); // function
Why this Matters
Today, with GenAI and agentic tools writing much of our code, it’s tempting to skip fundamentals. But when things break — or when something new comes up — understanding what’s really happening under the hood is the difference between struggling and thriving as a developer.
JavaScript’s OOP is a perfect example. It looks familiar, but it’s fundamentally different. Once you see past the illusion, you gain a deeper mastery of the language, and that’s what truly makes you a better engineer.
Top comments (0)