DEV Community

Cover image for Understanding Object-Oriented Programming in JavaScript
Pratham
Pratham

Posted on

Understanding Object-Oriented Programming in JavaScript

How to think in blueprints and objects — and write code that scales.


For a while, I wrote JavaScript the only way I knew: functions and variables scattered across a file, doing things step by step. It worked for small scripts, but the moment a project got bigger — more features, more data, more logic — the code turned into spaghetti. Functions everywhere, no clear structure, and I kept asking myself: "Where does this piece of logic belong?"

That's when I learned about Object-Oriented Programming (OOP), and it changed how I think about code. Instead of organizing by what the code does, I started organizing by what the code represents. Users, products, tasks — each one becomes a self-contained unit with its own data and behavior.

In the ChaiCode Web Dev Cohort 2026, OOP was the bridge between "writing code that works" and "writing code that's organized." Let me show you how it works in JavaScript.


What Does Object-Oriented Programming Mean?

Object-Oriented Programming is a way of writing code where you model real-world things as objects. Each object has:

  • Properties — the data it holds (what it is)
  • Methods — the actions it can perform (what it does)

Instead of writing loose functions and variables, you bundle related data and behavior together into objects.

Real-World Analogy: The Car Blueprint

Think about cars. Every car has certain properties: a brand, a model, a color, a speed. Every car can do certain things: start, stop, accelerate, honk.

Now, Toyota doesn't build each car from scratch. They have a blueprint — a design that defines what every Camry should have and what it can do. Then they produce individual cars from that blueprint. Each car is its own thing (different color, different owner), but they all share the same structure.

In OOP:

  • The blueprint is called a class
  • Each car produced from it is called an object (or instance)
Blueprint (Class):              Objects (Instances):
┌──────────────────┐           ┌─────────────────────┐
│      Car         │           │  myCar              │
│                  │    new    │  brand: "Toyota"    │
│  brand           │ ────→     │  model: "Camry"     │
│  model           │           │  color: "White"     │
│  color           │           └─────────────────────┘
│                  │           ┌─────────────────────┐
│  start()         │    new    │  yourCar            │
│  stop()          │ ────→     │  brand: "Honda"     │
│  accelerate()    │           │  model: "Civic"     │
└──────────────────┘           │  color: "Black"     │
                               └─────────────────────┘
One blueprint → many objects.
Same structure, different data.
Enter fullscreen mode Exit fullscreen mode

What Is a Class in JavaScript?

A class is the JavaScript syntax for creating blueprints. It was introduced in ES6 (2015) and provides a clean, readable way to define object structures and behaviors.

Basic Syntax

class ClassName {
  constructor(param1, param2) {
    this.property1 = param1;
    this.property2 = param2;
  }

  method1() {
    // do something
  }

  method2() {
    // do something else
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's break each piece down.


The constructor Method

The constructor is a special method that runs automatically when you create a new object from a class using new. Its job is to set up the initial properties of the object.

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

const pratham = new Person("Pratham", 22);
console.log(pratham.name); // "Pratham"
console.log(pratham.age); // 22
Enter fullscreen mode Exit fullscreen mode

What Happens Step by Step

new Person("Pratham", 22)

  1. A new empty object is created:  {}
  2. constructor() runs with this = that new object
  3. this.name = "Pratham"    { name: "Pratham" }
  4. this.age = 22            { name: "Pratham", age: 22 }
  5. The object is returned automatically

Result: pratham = { name: "Pratham", age: 22 }
Enter fullscreen mode Exit fullscreen mode

this inside the constructor refers to the new object being created. Every property you attach with this.something = value becomes a property on that specific instance.

Key Rules

  • Every class can have only one constructor
  • The constructor is optional — if your class doesn't need initial setup, you can skip it
  • The constructor doesn't return anything — the new keyword handles that

Methods Inside a Class

Methods are functions defined inside the class body. They define what the object can do.

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

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

  celebrateBirthday() {
    this.age++;
    console.log(`🎂 Happy Birthday, ${this.name}! You're now ${this.age}.`);
  }
}

const pratham = new Person("Pratham", 22, "Delhi");

pratham.introduce();
// "Hi, I'm Pratham, 22 years old, from Delhi."

pratham.celebrateBirthday();
// "🎂 Happy Birthday, Pratham! You're now 23."

pratham.introduce();
// "Hi, I'm Pratham, 23 years old, from Delhi."
Enter fullscreen mode Exit fullscreen mode

Methods Are Shared via Prototype

Here's something important: methods defined inside a class are automatically placed on the prototype, not on each individual instance. This means:

const p1 = new Person("Pratham", 22, "Delhi");
const p2 = new Person("Arjun", 21, "Mumbai");

console.log(p1.introduce === p2.introduce); // true ✅
Enter fullscreen mode Exit fullscreen mode

Both instances share the same introduce function in memory. The class handles this optimization for you — no duplicate functions, no wasted memory.


Creating Objects Using Classes

Creating objects from a class is as simple as using new:

class Car {
  constructor(brand, model, year) {
    this.brand = brand;
    this.model = model;
    this.year = year;
    this.isRunning = false;
  }

  start() {
    this.isRunning = true;
    console.log(`${this.brand} ${this.model} started! 🚗`);
  }

  stop() {
    this.isRunning = false;
    console.log(`${this.brand} ${this.model} stopped.`);
  }

  info() {
    const status = this.isRunning ? "Running" : "Off";
    console.log(`${this.year} ${this.brand} ${this.model}${status}`);
  }
}

const car1 = new Car("Toyota", "Camry", 2024);
const car2 = new Car("Honda", "Civic", 2023);
const car3 = new Car("Hyundai", "Creta", 2024);

car1.start(); // "Toyota Camry started! 🚗"
car1.info(); // "2024 Toyota Camry — Running"

car2.info(); // "2023 Honda Civic — Off"
car2.start(); // "Honda Civic started! 🚗"

car3.stop(); // "Hyundai Creta stopped."
Enter fullscreen mode Exit fullscreen mode

Three cars. One class. Each car is independent — starting car1 doesn't affect car2 or car3.

Class → Instance Relationship Visual

                    ┌──────────────────┐
                    │    Class: Car    │
                    │                  │
                    │  Properties:     │
                    │   brand, model,  │
                    │   year, isRunning│
                    │                  │
                    │  Methods:        │
                    │   start()        │
                    │   stop()         │
                    │   info()         │
                    └────────┬─────────┘
                             │
              ┌──────────────┼──────────────┐
              ↓              ↓              ↓
       new Car(...)    new Car(...)    new Car(...)
              ↓              ↓              ↓
     ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
     │    car1      │ │    car2      │ │    car3      │
     │              │ │              │ │              │
     │ brand:       │ │ brand:       │ │ brand:       │
     │  "Toyota"    │ │  "Honda"     │ │  "Hyundai"   │
     │ model:       │ │ model:       │ │ model:       │
     │  "Camry"     │ │  "Civic"     │ │  "Creta"     │
     │ year: 2024   │ │ year: 2023   │ │ year: 2024   │
     │ isRunning:   │ │ isRunning:   │ │ isRunning:   │
     │  true        │ │  true        │ │  false       │
     └──────────────┘ └──────────────┘ └──────────────┘

     Each instance has its OWN data.
     All instances SHARE the same methods (via prototype).
Enter fullscreen mode Exit fullscreen mode

Basic Idea of Encapsulation

Encapsulation is an OOP principle that means: bundle related data and methods together, and control access to them.

At its simplest level, encapsulation is what classes already do — they group properties and methods into a single, self-contained unit:

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

  deposit(amount) {
    if (amount <= 0) {
      console.log("Deposit amount must be positive.");
      return;
    }
    this.balance += amount;
    console.log(`${this.owner} deposited ₹${amount}. Balance: ₹${this.balance}`);
  }

  withdraw(amount) {
    if (amount > this.balance) {
      console.log("Insufficient balance! ❌");
      return;
    }
    this.balance -= amount;
    console.log(`${this.owner} withdrew ₹${amount}. Balance: ₹${this.balance}`);
  }

  getBalance() {
    console.log(`${this.owner}'s balance: ₹${this.balance}`);
  }
}

const account = new BankAccount("Pratham", 10000);

account.deposit(5000);
// "Pratham deposited ₹5000. Balance: ₹15000"

account.withdraw(3000);
// "Pratham withdrew ₹3000. Balance: ₹12000"

account.withdraw(20000);
// "Insufficient balance! ❌"

account.getBalance();
// "Pratham's balance: ₹12000"
Enter fullscreen mode Exit fullscreen mode

Why Encapsulation Matters

Instead of exposing raw data and letting anyone modify balance directly:

// ❌ Without encapsulation — anyone can do anything
account.balance = -99999; // No validation, no rules!
Enter fullscreen mode Exit fullscreen mode

You provide methods (deposit, withdraw) that enforce rules. The data is managed through defined behavior, not modified freely. This prevents bugs and makes the code predictable.

💡 Note: JavaScript also supports truly private properties using # syntax (#balance), but that's a more advanced topic. For now, understanding that encapsulation means "group data + behavior and control access through methods" is the key takeaway.


Why OOP? The Benefits

Benefit What It Means
Organization Related data and behavior live together, not scattered around
Reusability One class → many objects. Write the blueprint once, use it forever
Readability user.introduce() is clearer than introduceUser(user)
Maintainability Change the class, all instances get the updated behavior
Scalability Easy to add new features to a class without breaking existing code
Real-world modeling Code structure mirrors how we think about things naturally

Before OOP vs After OOP

// ❌ Before — scattered functions and data
const studentName = "Pratham";
const studentAge = 22;
const studentCourse = "Web Dev";

function introduceStudent(name, age, course) {
  console.log(`${name}, ${age}, ${course}`);
}

introduceStudent(studentName, studentAge, studentCourse);
// Works, but nothing connects these pieces

// ✅ After — everything bundled together
class Student {
  constructor(name, age, course) {
    this.name = name;
    this.age = age;
    this.course = course;
  }

  introduce() {
    console.log(`${this.name}, ${this.age}, ${this.course}`);
  }
}

const student = new Student("Pratham", 22, "Web Dev");
student.introduce();
// Clear ownership. Data and behavior together. Scalable.
Enter fullscreen mode Exit fullscreen mode

Let's Practice: Hands-On Assignment

Part 1: Create a Student Class

class Student {
  constructor(name, age, course) {
    this.name = name;
    this.age = age;
    this.course = course;
    this.grades = [];
  }

  introduce() {
    console.log(
      `Hi, I'm ${this.name}, ${this.age} years old, studying ${this.course}.`
    );
  }

  addGrade(grade) {
    this.grades.push(grade);
    console.log(`${this.name} received a grade: ${grade}`);
  }

  getAverage() {
    if (this.grades.length === 0) return "No grades yet.";
    const sum = this.grades.reduce((total, g) => total + g, 0);
    return (sum / this.grades.length).toFixed(1);
  }

  showReport() {
    console.log(`--- Report Card: ${this.name} ---`);
    console.log(`Course: ${this.course}`);
    console.log(`Grades: ${this.grades.join(", ") || "None"}`);
    console.log(`Average: ${this.getAverage()}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Part 2: Create Multiple Student Objects

const s1 = new Student("Pratham", 22, "Web Dev Cohort 2026");
const s2 = new Student("Arjun", 21, "Data Science");
const s3 = new Student("Priya", 23, "Machine Learning");

s1.introduce(); // "Hi, I'm Pratham, 22 years old, studying Web Dev Cohort 2026."
s2.introduce(); // "Hi, I'm Arjun, 21 years old, studying Data Science."
s3.introduce(); // "Hi, I'm Priya, 23 years old, studying Machine Learning."
Enter fullscreen mode Exit fullscreen mode

Part 3: Add Grades and View Reports

s1.addGrade(85);
s1.addGrade(92);
s1.addGrade(78);

s2.addGrade(90);
s2.addGrade(88);

s1.showReport();
// --- Report Card: Pratham ---
// Course: Web Dev Cohort 2026
// Grades: 85, 92, 78
// Average: 85.0

s2.showReport();
// --- Report Card: Arjun ---
// Course: Data Science
// Grades: 90, 88
// Average: 89.0

s3.showReport();
// --- Report Card: Priya ---
// Course: Machine Learning
// Grades: None
// Average: No grades yet.
Enter fullscreen mode Exit fullscreen mode

Part 4: Verify Independence

// Each student is independent
console.log(s1.grades); // [85, 92, 78]
console.log(s2.grades); // [90, 88]
console.log(s3.grades); // []

// Shared methods
console.log(s1.introduce === s2.introduce); // true ✅
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. OOP means organizing code around objects — bundling data (properties) and behavior (methods) together into self-contained units.
  2. A class is a blueprint. An object/instance is a specific thing built from that blueprint. One class → many instances.
  3. The constructor runs automatically on new and sets up each instance's initial properties using this.
  4. Methods defined in a class are shared across all instances via the prototype — efficient and memory-friendly.
  5. Encapsulation means grouping related data and methods together and controlling access through defined behavior, not raw manipulation.

Wrapping Up

Object-Oriented Programming isn't just a technique — it's a way of thinking. Instead of asking "what steps does my program need to follow?", you ask "what things does my program work with, and what can those things do?" That shift in thinking leads to code that's more organized, more reusable, and more closely mirrors how the real world works.

I'm learning all of this through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg, and OOP was the point where JavaScript started feeling like a real software engineering language — not just a scripting tool. Classes, constructors, methods, encapsulation — these patterns are everywhere in React, Node.js, and modern frameworks.

Connect with me on LinkedIn or visit PrathamDEV.in. More articles on the way as I keep leveling up.

Happy coding! 🚀


Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode

Top comments (0)