OOP in JavaScript: A Beginner's Guide
Master classes, objects, constructors, and encapsulation in JavaScript — with hands-on exercises and real-world examples.

👋 Hey, I'm Mohd Kaif – a student documenting my journey through code. I write about what I'm learning in real-time – the wins, the struggles, and the "aha!" moments. From JavaScript and React to backend systems with Node.js, databases, DevOps, TypeScript, and AI integrations. This blog is my public learning journal: honest, evolving, and always exploring. If you're curious about any of these topics, let's learn and build together!
Have you ever looked at your own code a few weeks later and thought — "Who wrote this mess?"
You scroll through hundreds of lines of loosely connected variables and functions, trying to trace where something broke. It's frustrating, slow, and feels like untangling headphone cables in the dark.
If any of these sound familiar, you're in the right place:
"My code works, but adding one new feature feels like defusing a bomb."
"I keep copy-pasting the same logic in different places."
"I've heard of OOP, but I don't know when or why to actually use it."
"I understand classes in theory, but I can't connect them to real code I'd actually write."
The problem isn't that you're a bad developer. The problem is that no one showed you a clean mental model for organizing code as it grows. Object-Oriented Programming (OOP) is that model — and once it clicks, you'll never look at a codebase the same way again.
✅ What You'll Learn
What OOP actually means — beyond the textbook definition
How to use the blueprint analogy to make classes and objects feel obvious
How to write a class in JavaScript, step by step
What constructors and methods do, and how
thisties them togetherWhy encapsulation matters — with a before/after code comparison
When to use OOP (and when not to)
🚫 No prerequisites required. If you know basic JavaScript — variables, functions, and loops — you're ready.
What Is Object-Oriented Programming?
At its core, OOP is a way of organizing code around objects — bundles that combine related data and behavior in one place.
Instead of having scattered variables (studentName, studentAge) and disconnected functions (printStudentDetails()), you group everything that belongs together into a single, tidy unit.
The payoff? Your code starts to mirror the real world — and the real world is a lot easier to reason about than a pile of loose functions.
The Blueprint Analogy (And Why It Works So Well)
Think about how cars are manufactured.
An engineer doesn't build each car from scratch by hand. They design a blueprint once — specifying the engine size, color options, and features. Then that same blueprint is used to produce hundreds of cars. Each car is its own physical object, with its own color and mileage, but they all share the same underlying structure.
That's exactly how classes and objects work in JavaScript.
| Real World | JavaScript |
|---|---|
| Blueprint | Class |
| A specific car | Object (Instance) |
| Features (color, speed) | Properties |
| Actions (drive, brake) | Methods |
Car (Blueprint / Class)
───────────────────────
brand
speed
drive()
↓ new Car(...)
─────────────────────────────────
| | |
Car #1 Car #2 Car #3
brand: Toyota brand: BMW brand: Ford
speed: 120 speed: 150 speed: 130
Each car is its own independent object — but they all share the same structure defined by the class.
Writing Your First Class
Here's how that blueprint looks in JavaScript:
class Car {
constructor(brand, speed) {
this.brand = brand;
this.speed = speed;
}
drive() {
console.log(`\({this.brand} is driving at \){this.speed} km/h`);
}
}
Let's break down what each part does:
class Car— declares the blueprintconstructor(...)— runs automatically when you create a new car; sets its initial datathis.brand— refers to this specific car's brand (not the blueprint's)drive()— a method that defines what any car can do
🔑 Key insight:
thisalways refers to the specific object being worked with — like saying "this particular car" instead of "cars in general."
Creating Objects From a Class
Once you have a class, you create objects using the new keyword:
const car1 = new Car("Toyota", 120);
const car2 = new Car("BMW", 150);
car1.drive(); // Toyota is driving at 120 km/h
car2.drive(); // BMW is driving at 150 km/h
Each call to new Car(...) uses the blueprint to stamp out a fresh object with its own data. Change car1's speed and car2 is completely unaffected — they're independent objects.
⚠️ Common mistake: Forgetting
newis one of the most frequent bugs beginners run into.const car = Car("Toyota", 120); // ❌ Won't work as expected const car = new Car("Toyota", 120); // ✅ Correct
🏋️ Quick Exercise #1
Try this before reading on:
Create a class called
Animalwith propertiesnameandsoundAdd a method called
speak()that logs: "[name] says [sound]"Create two animal objects — a dog and a cat — and call
speak()on each
✅ See the answer
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
console.log(`\({this.name} says \){this.sound}`);
}
}
const dog = new Animal("Dog", "Woof");
const cat = new Animal("Cat", "Meow");
dog.speak(); // Dog says Woof
cat.speak(); // Cat says Meow
The Constructor Method, Up Close
The constructor is a special method — it's the first thing that runs when you create a new object. Think of it as the car's spec sheet being filled in at the factory.
constructor(brand, speed) {
this.brand = brand; // Assign brand to this specific car
this.speed = speed; // Assign speed to this specific car
}
A few things worth knowing:
If you don't write a constructor, JavaScript creates a silent, empty one for you
You can set default values right in the constructor
You can run any setup logic here — not just property assignments
class Car {
constructor(brand, speed = 100) { // Default speed: 100
this.brand = brand;
this.speed = speed;
this.mileage = 0; // Always starts at 0
}
}
const myCar = new Car("Honda"); // No speed needed
console.log(myCar.speed); // 100
console.log(myCar.mileage); // 0
Methods: Giving Objects Behavior
Methods are functions that live inside a class. They define what objects of that class can do.
Here's a Student class that we'll build on throughout the rest of this article:
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
printDetails() {
console.log(`Name: \({this.name}, Age: \){this.age}`);
}
}
You can add as many methods as you need:
class Student {
constructor(name, age, grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
printDetails() {
console.log(`Name: \({this.name}, Age: \){this.age}`);
}
isPassingGrade() {
return this.grade >= 50;
}
describe() {
const status = this.isPassingGrade() ? "passing" : "failing";
console.log(`\({this.name} is currently \){status}.`);
}
}
const student1 = new Student("Rahul", 20, 72);
const student2 = new Student("Priya", 22, 45);
student1.describe(); // Rahul is currently passing.
student2.describe(); // Priya is currently failing.
Notice how methods can call other methods inside the same class using this. The describe() method calls this.isPassingGrade() — keeping logic clean and reusable.
🏋️ Quick Exercise #2
Extend the Student class above:
Add a property called
courses(an array of course names) in the constructorAdd a method
enroll(course)that adds a course to the arrayAdd a method
listCourses()that logs all enrolled coursesCreate a student, enroll them in two courses, and call
listCourses()
✅ See the answer
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
this.courses = [];
}
enroll(course) {
this.courses.push(course);
console.log(`\({this.name} enrolled in \){course}`);
}
listCourses() {
console.log(`\({this.name}'s courses: \){this.courses.join(", ")}`);
}
}
const s = new Student("Aman", 19);
s.enroll("JavaScript");
s.enroll("HTML & CSS");
s.listCourses(); // Aman's courses: JavaScript, HTML & CSS
Encapsulation: The Part Most Tutorials Skip
Encapsulation is the idea of bundling data and methods together, and controlling how that data is accessed or changed.
This might sound abstract, so let's make it concrete with a before/after example.
❌ Without Encapsulation
let balance = 1000;
function deposit(amount) {
balance += amount;
}
function withdraw(amount) {
balance -= amount;
}
// Nothing stops someone from doing this:
balance = -99999; // 💣 Direct manipulation — no safeguards
With scattered variables and functions, anything can touch your data. One accidental assignment and your balance is -$99,999.
✅ With Encapsulation
class BankAccount {
constructor(initialBalance) {
this.balance = initialBalance;
}
deposit(amount) {
if (amount <= 0) {
console.log("Deposit amount must be positive.");
return;
}
this.balance += amount;
console.log(`Deposited $${amount}. New balance: $${this.balance}`);
}
withdraw(amount) {
if (amount > this.balance) {
console.log("Insufficient funds.");
return;
}
this.balance -= amount;
console.log(`Withdrew $${amount}. New balance: $${this.balance}`);
}
getBalance() {
return this.balance;
}
}
const account = new BankAccount(1000);
account.deposit(500); // Deposited \(500. New balance: \)1500
account.withdraw(200); // Withdrew \(200. New balance: \)1300
account.withdraw(9999); // Insufficient funds.
Now the balance is only changed through controlled methods that include validation logic. No outside code can randomly set balance = -99999 and break everything.
Why encapsulation matters:
Prevents accidental (or malicious) data corruption
Makes your code's behavior predictable
Easier to debug — you know exactly where data can change
The Full Student Example, Start to Finish
Let's put everything together. Here's the complete Student class, building every concept we've covered:
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
this.courses = [];
}
printDetails() {
console.log(`Name: \({this.name}, Age: \){this.age}`);
}
enroll(course) {
this.courses.push(course);
}
listCourses() {
if (this.courses.length === 0) {
console.log(`${this.name} has no enrolled courses.`);
return;
}
console.log(`\({this.name}'s courses: \){this.courses.join(", ")}`);
}
}
// Create multiple student objects
const student1 = new Student("Rahul", 20);
const student2 = new Student("Priya", 22);
const student3 = new Student("Aman", 19);
student1.enroll("JavaScript");
student1.enroll("Node.js");
student2.enroll("Python");
student1.printDetails(); // Name: Rahul, Age: 20
student1.listCourses(); // Rahul's courses: JavaScript, Node.js
student2.printDetails(); // Name: Priya, Age: 22
student2.listCourses(); // Priya's courses: Python
student3.printDetails(); // Name: Aman, Age: 19
student3.listCourses(); // Aman has no enrolled courses.
Student (Class)
───────────────────────────────
name
age
courses []
printDetails()
enroll()
listCourses()
↓ new Student(...)
─────────────────────────────────────────────
| | |
Student1 Student2 Student3
name: "Rahul" name: "Priya" name: "Aman"
age: 20 age: 22 age: 19
courses: [JS, Node] courses: [Python] courses: []
🏋️ Quick Exercise #3 — Your Turn
Build a Library class from scratch:
It should have a property
books(an array), initialized as emptyAdd an
addBook(title)method that adds a book to the arrayAdd a
listBooks()method that logs all available booksAdd a
removeBook(title)method that removes a book by titleCreate a library, add 3 books, remove 1, and list what's left
This is the kind of real-world object you'd model in an actual app — give it a try!
✅ See the answer
class Library {
constructor() {
this.books = [];
}
addBook(title) {
this.books.push(title);
console.log(`"${title}" added to the library.`);
}
removeBook(title) {
const index = this.books.indexOf(title);
if (index === -1) {
console.log(`"${title}" not found.`);
return;
}
this.books.splice(index, 1);
console.log(`"${title}" removed.`);
}
listBooks() {
console.log("Available books:", this.books.join(", "));
}
}
const lib = new Library();
lib.addBook("JavaScript: The Good Parts");
lib.addBook("Clean Code");
lib.addBook("You Don't Know JS");
lib.removeBook("Clean Code");
lib.listBooks(); // JavaScript: The Good Parts, You Don't Know JS
When Should You Use OOP?
OOP is a powerful tool — but not every nail needs this hammer.
Use OOP when:
You're modeling real-world entities (Users, Products, Orders, Accounts)
You need multiple instances of something with the same structure
Data and behavior naturally belong together
Your codebase is growing and needs organized structure
Skip OOP when:
You're writing a small, one-off script
The logic is purely functional (transforming data, filtering arrays)
Adding a class would make things more complex, not less
The goal of OOP isn't to use classes everywhere — it's to use them where they genuinely make your code cleaner and more maintainable.
What to Learn Next
Now that you understand classes, objects, constructors, methods, and encapsulation, here's where to go next:
Inheritance — Learn how one class can extend another to reuse and build on existing logic (e.g., a
GraduateStudentthat inherits fromStudent)Prototypes — Understand what's happening under the hood when JavaScript creates objects, so class behavior never surprises you
Polymorphism — Discover how different objects can share the same method name but behave differently — a powerful pattern in real systems
Private Fields (
#) — Take encapsulation further with JavaScript's native syntax for truly private properties that can't be touched from outside a class
Each of these builds directly on what you've just learned — you're already halfway there.
💬 Got Questions?
Drop a comment below! I'd love to hear what you're building or help you work through anything that didn't click.
Here are topics coming up in future articles:
Inheritance in JavaScript: How to build child classes that reuse and extend parent logic without repeating yourself
Understanding Prototypes: What's really happening when you write
new, and how JavaScript's prototype chain actually worksOOP Design Patterns: Practical patterns like Factory, Singleton, and Observer that show up in real codebases
Building a Mini-App with OOP: A hands-on project that ties classes, encapsulation, and inheritance together into something you can ship
Found this helpful? Share it with someone just getting started with JavaScript — and follow along so you don't miss the next one. 🚀




