DEV Community

Cover image for The Great OOP Illusion in JS
Yash Chavan
Yash Chavan

Posted on • Originally published at hunt092.hashnode.dev

The Great OOP Illusion in JS

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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, and new Human() creates a plain JS object linked to Human.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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)