Skip to main content

Command Palette

Search for a command to run...

Understanding Object-Oriented Programming in JavaScript

A beginner-friendly guide to thinking in objects — with real analogies, clean code, and zero jargon.

Updated
10 min read
C
Software developer passionate about building scalable web applications with React and backend technologies. I enjoy solving problems, building projects, and sharing my learning with the community.

You've written functions. You've worked with variables. And at some point, someone told you to "learn OOP." You googled it, saw words like encapsulation, abstraction, polymorphism — and quietly closed the tab.

This blog is the one you should have found instead.

We're going to learn Object-Oriented Programming the way it was meant to be understood — starting with how you already think about the real world, then translating that thinking into JavaScript code.

No jargon walls. No confusing theory first. Just concepts that click.


The Problem OOP Solves

Imagine you're building an app that manages students in a school. Each student has a name, an age, and a grade. They can also do things — like introduce themselves or get promoted to the next grade.

Without OOP, you might write something like this:

let student1Name = "Rahul";
let student1Age = 16;
let student1Grade = 10;

let student2Name = "Priya";
let student2Age = 15;
let student2Grade = 9;

function introduceStudent1() {
  console.log(`Hi, I'm \({student1Name}, I'm in grade \){student1Grade}.`);
}

function introduceStudent2() {
  console.log(`Hi, I'm \({student2Name}, I'm in grade \){student2Grade}.`);
}

This works for two students. But what about 500? What if you need to add a new property — say, rollNumber — to every student? You'd have to change hundreds of lines.

This is the problem OOP solves. Instead of scattering related data and functions across your code, OOP lets you bundle them together into a single, reusable unit called an object.


The Blueprint Analogy — Understanding Classes

Think about a car factory.

Before a single car rolls off the assembly line, engineers design a blueprint. The blueprint says: every car will have an engine, four wheels, a colour, and a speed. It also describes what a car can do — accelerate, brake, honk.

The blueprint itself is not a car. You can't drive a blueprint. But from that one blueprint, the factory can produce thousands of cars. Each car is physically separate — one is red, one is blue, one belongs to Rahul, one belongs to Priya — but they all share the same structure because they came from the same blueprint.

In JavaScript:

  • The blueprint is called a class

  • Each car produced from that blueprint is called an object (or an instance)

  • The process of creating an object from a class is called instantiation

This is the entire mental model of OOP. Everything else is just details on top of this idea.


Your First Class in JavaScript

Here's how you write a class in JavaScript:

class Car {
  constructor(brand, colour, speed) {
    this.brand = brand;
    this.colour = colour;
    this.speed = speed;
  }
}

Let's read this carefully.

class Car tells JavaScript: we are defining a blueprint, and we're calling it Car.

constructor is a special method that runs automatically the moment you create a new car from this blueprint. It's like the assembly line — the moment a new car is being built, the constructor runs and sets everything up.

this refers to the specific car being created right now. When you write this.brand = brand, you're saying: "take the brand value passed in, and store it as a property of this particular car."

Now let's actually create some cars:

const car1 = new Car("Toyota", "red", 120);
const car2 = new Car("Honda", "blue", 100);

console.log(car1.brand);   // Toyota
console.log(car2.colour);  // blue

The keyword new is what triggers the whole process. It tells JavaScript: "create a fresh object using the Car blueprint, run the constructor with these values, and give me back the finished object."

car1 and car2 are now two completely independent objects. Changing one doesn't affect the other — just like driving one car doesn't affect every other car built from the same blueprint.


Adding Methods — Teaching Objects to Do Things

So far our Car objects hold data. But real objects also have behaviour — things they can do. In OOP, these behaviours are called methods, and they're just functions written inside the class.

class Car {
  constructor(brand, colour, speed) {
    this.brand = brand;
    this.colour = colour;
    this.speed = speed;
  }

  accelerate(amount) {
    this.speed += amount;
    console.log(`\({this.brand} is now going \){this.speed} km/h.`);
  }

  describe() {
    console.log(`This is a \({this.colour} \){this.brand} going at ${this.speed} km/h.`);
  }
}

Now let's use them:

const myCar = new Car("Toyota", "red", 60);

myCar.describe();        // This is a red Toyota going at 60 km/h.
myCar.accelerate(30);    // Toyota is now going at 90 km/h.
myCar.describe();        // This is a red Toyota going at 90 km/h.

Notice something important: this inside a method refers to the specific object the method was called on. When you call myCar.accelerate(30), this.speed inside the method refers to myCar's speed — not any other car's.

This is the heart of OOP: data and the functions that operate on that data live together, inside the same object.


A More Relatable Example — The Person Class

Let's try a Person class, since we'll use this idea constantly:

class Person {
  constructor(name, age, city) {
    this.name = name;
    this.age = age;
    this.city = city;
  }

  greet() {
    console.log(`Hi! I'm \({this.name}, I'm \){this.age} years old, from ${this.city}.`);
  }

  birthday() {
    this.age += 1;
    console.log(`Happy birthday, \({this.name}! You're now \){this.age}.`);
  }
}

const person1 = new Person("Rahul", 20, "Mumbai");
const person2 = new Person("Priya", 22, "Delhi");

person1.greet();    // Hi! I'm Rahul, I'm 20 years old, from Mumbai.
person2.greet();    // Hi! I'm Priya, I'm 22 years old, from Delhi.

person1.birthday(); // Happy birthday, Rahul! You're now 21.
person1.greet();    // Hi! I'm Rahul, I'm 21 years old, from Mumbai.

Calling birthday() on person1 only changes person1's age. person2 is completely unaffected. Each object manages its own state.

And when you need a third person? You just write one more line:

const person3 = new Person("Ananya", 19, "Pune");

No duplicate code. No new functions. The class handles everything.


The Student Class — Putting It All Together

Now let's go back to the school problem from the beginning and solve it properly with OOP:

class Student {
  constructor(name, age, grade, rollNumber) {
    this.name = name;
    this.age = age;
    this.grade = grade;
    this.rollNumber = rollNumber;
  }

  introduce() {
    console.log(
      `Hi! I'm \({this.name}, Roll No. \){this.rollNumber}, studying in Grade ${this.grade}.`
    );
  }

  promote() {
    this.grade += 1;
    console.log(`Congratulations \({this.name}! You've been promoted to Grade \){this.grade}.`);
  }

  study(subject) {
    console.log(`\({this.name} is studying \){subject}.`);
  }
}

// Creating student objects
const student1 = new Student("Rahul", 16, 10, "A101");
const student2 = new Student("Priya", 15, 9, "B204");

student1.introduce();    // Hi! I'm Rahul, Roll No. A101, studying in Grade 10.
student2.introduce();    // Hi! I'm Priya, Roll No. B204, studying in Grade 9.

student1.promote();      // Congratulations Rahul! You've been promoted to Grade 11.
student2.study("Maths"); // Priya is studying Maths.

Now adding a third student is one line. Adding a new method applies to every student automatically. Adding a new property means updating the constructor once, not a hundred variable declarations.

That's the power of reusability.


Understanding Encapsulation

Here's an idea that sounds complicated but is actually very natural: encapsulation.

Encapsulation simply means: keep the data and the code that works with that data together, in one place.

Think about your phone. You don't need to understand how the battery, processor, and display work internally. You just press a button and it does something. The complex internal workings are hidden — encapsulated — inside the device.

In OOP, your class is the phone. The data (this.name, this.age) is the internal workings. The methods (greet(), birthday()) are the buttons. Users of your class don't need to dig into the internal data — they just call the methods.

class BankAccount {
  constructor(owner, balance) {
    this.owner = owner;
    this.balance = balance; // internal data
  }

  deposit(amount) {
    if (amount > 0) {
      this.balance += amount;
      console.log(`Deposited ₹\({amount}. New balance: ₹\){this.balance}`);
    }
  }

  withdraw(amount) {
    if (amount > this.balance) {
      console.log("Insufficient funds.");
    } else {
      this.balance -= amount;
      console.log(`Withdrew ₹\({amount}. New balance: ₹\){this.balance}`);
    }
  }

  checkBalance() {
    console.log(`Balance for \({this.owner}: ₹\){this.balance}`);
  }
}

const myAccount = new BankAccount("Rahul", 5000);

myAccount.deposit(2000);    // Deposited ₹2000. New balance: ₹7000
myAccount.withdraw(1000);   // Withdrew ₹1000. New balance: ₹6000
myAccount.checkBalance();   // Balance for Rahul: ₹6000

Notice what we're doing here: the logic for checking if a withdrawal is valid lives inside the class. Whoever uses BankAccount doesn't need to write that check themselves — it's already baked in. That's encapsulation in practice.


Why OOP Makes Your Code Better

Here's a side-by-side comparison that makes the benefit crystal clear:

Without OOP — 4 students, already messy:

let s1Name = "Rahul", s1Age = 16, s1Grade = 10;
let s2Name = "Priya", s2Age = 15, s2Grade = 9;
let s3Name = "Ananya", s3Age = 17, s3Grade = 11;
let s4Name = "Dev", s4Age = 16, s4Grade = 10;

function introduceStudent(name, grade) {
  console.log(`Hi I'm \({name}, Grade \){grade}`);
}

With OOP — 400 students, same code:

class Student {
  constructor(name, age, grade) {
    this.name = name;
    this.age = age;
    this.grade = grade;
  }
  introduce() {
    console.log(`Hi I'm \({this.name}, Grade \){this.grade}`);
  }
}

const students = [
  new Student("Rahul", 16, 10),
  new Student("Priya", 15, 9),
  // ... 398 more lines, all the same structure
];

students.forEach(s => s.introduce());

Same result. Infinitely more scalable. If you need to change what introduce() does, you change it in exactly one place.


The Mental Shift OOP Asks You to Make

Before OOP, you think in terms of actions first — what do I want to do, and what data do I need for it?

With OOP, you think in terms of things first — what are the entities in my system, what data do they hold, and what can they do?

A school app has Students, Teachers, Courses, and Classes. An e-commerce app has Products, Orders, Users, and Carts. A game has Players, Enemies, Weapons, and Levels.

Each of these becomes a class. Each class holds its own data and its own behaviour. Your job as a developer is to design these blueprints well — and then let JavaScript create as many objects as you need from them.

That shift in thinking is what Object-Oriented Programming really teaches you.


Quick Reference — Everything We Covered

class ClassName {             // define the blueprint
  constructor(param1, param2) {  // runs when you use `new`
    this.property1 = param1;     // attach data to the object
    this.property2 = param2;
  }

  methodName() {              // define behaviour
    console.log(this.property1);
  }
}

const myObject = new ClassName("value1", "value2"); // create an object
myObject.methodName();        // use the object

Summary

Object-Oriented Programming is a way of organising code that mirrors how we naturally think about the world. Real things have properties (data) and behaviours (methods) — and OOP lets you model exactly that in code.

A class is the blueprint — it defines what properties and methods every object of that type will have. An object is a specific instance created from that blueprint — it has its own data but shares the structure. The constructor is the setup function that runs every time a new object is created. Methods are functions inside a class that define what an object can do. Encapsulation means bundling the data and the code that operates on it together, and hiding the complexity from the outside.

You now have everything you need to start thinking in objects. The next time you're designing a feature, ask yourself: what are the things in this system? The answer to that question is where your classes begin.


Found this helpful? Follow for more JavaScript concepts explained clearly. Next up: Inheritance — how one class can build on top of another without repeating a single line of code.


More from this blog

Why Node.js is Perfect for Building Fast Web Applications

Every technology makes a bet. Node.js's bet was this: most web applications aren't slow because they do too much computation. They're slow because they spend most of their time waiting — waiting for a database to respond, waiting for a file to load, waiting for an external API to return. If you build a runtime optimised around that specific reality, you get something genuinely fast for the work most web apps actually do.

May 9, 202611 min read1
C

Chetan Chauhan | Tech Blog | chetan71

41 posts