If you’ve been programming for a decade or you’re just getting started, you’ve probably heard of OOP (Object-Oriented Programming). It’s a powerful paradigm that support many modern programming languages, from Java to C#.
But when it comes to JavaScript, things are interesting. JavaScript wasn’t originally built with classes in mind—it was designed as a lightweight scripting language for browsers. Over time, as web applications became more complex, JavaScript evolved. Today, with ES6 and beyond, it supports OOP-like patterns while still being fundamentally prototype-based under the hood.
In this blog, we’ll explore:
1 . What OOP is
2 . The core concepts of OOP
3 . How OOP is implemented in JavaScript
4 . A real-world example you probably already use
What is OOP?
Object-Oriented Programming is a paradigm that organizes code around objects. An object represents a real-world entity (like a user, product, or order) and bundles both data (properties) and behavior (methods) together.
In programming, we treat every thing :
As a JavaScript developer, you can think of an object as a collection of related data and behavior.
An object is made up of:
1 . Properties (data): key–value pairs (like variables inside the object).
2 . Methods (behavior): functions stored as properties, which define what the object can do.
In simple terms: A property describes what it has and a method describes what it does.
Benefits of OOP:
1 . Reusability: Write once, use across projects.
2 . Encapsulation: Hide implementation details, expose only what matters.
3 . Maintainability: Easier to update and extend.
4 . Abstraction: Focus on “what” an object does, not “how” it does it.
5 . Polymorphism: Same method name, different behavior depending on context.
Concpets in OOP
1 . Object
2 . Class & constructors
3 . Inheritance
4 . Polymorphism
5 . Abstraction
6 . Encapsulation
JavaScript and OOPs concept
In Modern JavaScript, we can implement OOP concepts. As an engineer, you should have the goal of using OOP for various benefits. Eg: for reusability, encapsulation, inheritance, and so on. Remember these are the programming goals you should as an progarmmer you should have goals.
Do we need to implement all these concepts? No..but as needed.
1. Object
Objects are the foundation of OOP. In JavaScript, before modern JavaScript we used to refer JavaScript as prototypal scripting language. As everything used to be done through prototype. In JavaScript, we make an object like this. Now, from JS pov, why this is important. As it helps in have a strucuture your code , data, properties and methods.
// creating an object in JavaScript
const car = {
brand: "Tesla", // data
model: "Model 3", // data
drive() { // method
console.log(`${this.brand} ${this.model} is driving...`);
}
};
car.drive(); // Tesla Model 3 is driving...
The “Car” example explains OOP well, but it doesn’t feel very connected to everyday JavaScript work. So let’s model something practical—a carousel. In JavaScript, we can represent a carousel as an object.
// Creating a Carousel using object
const Carousel = {
images: ["image1.jpg", "image2.jpg", "image3.jpg"],
style: "default",
createCarousel() {
console.log("Carousel created with images:", this.images);
},
nextImage(index) {
console.log("Showing:", this.images[index % this.images.length]);
}
};
// Using the object
Carousel.createCarousel();
2 . Classes & Constructors
Before ES6, JavaScript didn’t have the class
or constructor
keywords.
Developers who came from languages like Java or PHP often mimicked these concepts using functions and prototypes.
With ES6, JavaScript introduced the class
syntax.
It doesn’t add new functionality under the hood — classes are syntactic sugar over prototypes — but it makes object-oriented code more readable and familiar.
The constructor
is a special method that runs automatically when you create a new instance of a class.
Class
andconstructors
form the foundation for the rest of the OOP concepts we’ll cover — from inheritance to abstraction.
class Car {
constructor(brand, model) {
this.brand = brand;
this.model = model;
}
drive() {
console.log(`${this.brand} ${this.model} is driving...`);
}
}
const myCar = new Car("Tesla", "Model Y");
myCar.drive(); // Tesla Model Y is driving...
Here, Car
is a class, and the constructor sets up its properties. When we call new Car("Tesla", "Model Y"), the constructor runs automatically.
class Carousel {
constructor(items = []) {
this.items = items;
this.currentIndex = 0;
}
showCurrent() {
console.log(`Showing: ${this.items[this.currentIndex]}`);
}
next() {
this.currentIndex = (this.currentIndex + 1) % this.items.length;
this.showCurrent();
}
prev() {
this.currentIndex =
(this.currentIndex - 1 + this.items.length) % this.items.length;
this.showCurrent();
}
goTo(index) {
if (index >= 0 && index < this.items.length) {
this.currentIndex = index;
this.showCurrent();
} else {
console.log("Invalid index");
}
}
}
const carousel = new Carousel(["img1.jpg", "img2.jpg", "img3.jpg"]);
carousel.showCurrent();
carousel.next();
carousel.goTo(2);
Here, Carousel
organizes state (items
, currentIndex
) and exposes methods to control the slideshow. The constructor ensures every new carousel starts in a valid state.
Classes and constructors in ES6 make object-oriented JavaScript cleaner and more intuitive, while still being powered by the prototype system under the hood.
3 . Inheritance (Prototypes)
Inheritance allows a child class to reuse and extend functionality from a parent class. It supports the DRY (Don’t Repeat Yourself) principle by letting you share code instead of duplicating it.
In JavaScript, inheritance was originally implemented using prototypes. Since ES6, we have the class
syntax, which makes inheritance much more intuitive though under the hood, it still uses prototypes.
class Vehicle {
constructor(type) {
this.type = type;
}
start() {
console.log(`${this.type} is starting...`);
}
}
class Car extends Vehicle {
constructor(brand) {
super("Car"); // call parent constructor
this.brand = brand;
}
start() { // method overriding
console.log(`${this.brand} ${this.type} is revving up!`);
}
}
const myCar = new Car("Tesla");
myCar.start(); // Tesla Car is revving up!
Here, Car
inherits from Vehicle
and reuses its constructor logic. It also overrides the start()
method to provide custom behavior.
Suppose we have a base Carousel
class. We can extend it to create specialized carousels with extra features like styles or auto-scroll.
class Carousel {
constructor(items = []) {
this.items = items;
this.currentIndex = 0;
}
showCurrent() {
console.log(`Showing: ${this.items[this.currentIndex]}`);
}
next() {
this.currentIndex = (this.currentIndex + 1) % this.items.length;
this.showCurrent();
}
}
class ImageCarousel extends Carousel {
constructor(images = [], style = "default") {
super(images); // call parent constructor
this.style = style;
}
showCurrent() { // override method
console.log(
`Displaying image [${this.style} style]: ${this.items[this.currentIndex]}`
);
}
}
const imgCarousel = new ImageCarousel(
["img1.jpg", "img2.jpg", "img3.jpg"],
"dark"
);
imgCarousel.showCurrent();
imgCarousel.next();
Here, ImageCarousel
extends Carousel
, reusing its logic for navigation, but overrides showCurrent()
to add styling context.
Inheritance lets you reuse and extend base functionality. Overriding allows subclasses to adapt behavior to their needs.
Even with ES6 classes, JavaScript’s inheritance model is still prototype based under the hood.
4 . Polymorphism
Polymorphism allows different classes to define their own versions of the same method. In practice, a child class can override a method from its parent class to provide specialized behavior.
Polymorphism and inheritance often go hand in hand:
Inheritance lets you reuse a base structure (
Vehicle
→Car
,Bike
).Polymorphism lets each subclass override behavior to suit its needs.
If you’re ever confused: Inheritance = reusing code and Polymorphism = redefining behavior.
class Vehicle {
drive() {
console.log("This vehicle is moving...");
}
}
class Car extends Vehicle {
drive() {
console.log("Car is driving on the road...");
}
}
class Bike extends Vehicle {
drive() {
console.log("Bike is zooming through traffic...");
}
}
const myCar = new Car();
const myBike = new Bike();
myCar.drive(); // Car is driving on the road...
myBike.drive(); // Bike is zooming through traffic...
In our caraousel example
class Carousel {
show() {
console.log("Showing carousel item...");
}
}
class ImageCarousel extends Carousel {
show() {
console.log("Displaying an image slide...");
}
}
class VideoCarousel extends Carousel {
show() {
console.log("Playing a video slide...");
}
}
const imgCarousel = new ImageCarousel();
const vidCarousel = new VideoCarousel();
imgCarousel.show(); // Displaying an image slide...
vidCarousel.show(); // Playing a video slide...
Here, both ImageCarousel
and VideoCarousel
extend the same Carousel
base class, but each redefines the show()
method — this is polymorphism in action.
5. Abstraction
Abstraction is about hiding implementation details and exposing only what’s necessary. It lets you define a contract (what methods must exist) without forcing subclasses to care about how they’re implemented.
JavaScript doesn’t have native abstract classes like Java or C#, but we can simulate abstraction by:
Throwing errors in base methods that must be overridden.
Using documentation or patterns to enforce a contract.
class Vehicle {
start() {
throw new Error("Method 'start()' must be implemented");
}
}
class Car extends Vehicle {
start() {
console.log("Car engine started...");
}
}
class Bike extends Vehicle {
start() {
console.log("Bike engine started...");
}
}
const myCar = new Car();
const myBike = new Bike();
myCar.start(); // Car engine started...
myBike.start(); // Bike engine started...
Here, Vehicle
enforces that every subclass must define start()
, but the actual implementation is left to Car
and Bike
.
class BaseCarousel {
render() {
throw new Error("Method 'render()' must be implemented");
}
}
class ImageCarousel extends BaseCarousel {
render() {
console.log("Rendering image carousel...");
}
}
class VideoCarousel extends BaseCarousel {
render() {
console.log("Rendering video carousel...");
}
}
const imgCarousel = new ImageCarousel();
const vidCarousel = new VideoCarousel();
imgCarousel.render(); // Rendering image carousel...
vidCarousel.render(); // Rendering video carousel...
Here, BaseCarousel
defines the abstract method render()
. Every type of carousel must implement it, but how it renders (images, videos, etc.) is up to the subclass.
6. Encapsulation
Encapsulation is about hiding internal details and exposing only what’s necessary through a public API. It prevents direct access to an object’s state, which makes code more secure, modular, and maintainable.
In modern JavaScript (ES2022+), we can achieve encapsulation using private fields (denoted by #
), getters, and setters.
class Car {
#fuel = 0; // private field
refuel(liters) {
this.#fuel += liters;
console.log(`Added ${liters}L of fuel.`);
}
drive() {
if (this.#fuel > 0) {
this.#fuel--;
console.log("Car is driving...");
} else {
console.log("No fuel left!");
}
}
getFuelLevel() {
return this.#fuel;
}
}
const myCar = new Car();
myCar.refuel(2);
myCar.drive(); // Car is driving...
console.log(myCar.getFuelLevel()); // 1
// myCar.#fuel (Error: private field)
The car’s fuel state is hidden. You can only change it through refuel()
and check it with getFuelLevel()
.
class Carousel {
#currentIndex = 0; // private field
constructor(items = []) {
this.items = items;
}
next() {
this.#currentIndex = (this.#currentIndex + 1) % this.items.length;
this.show();
}
prev() {
this.#currentIndex =
(this.#currentIndex - 1 + this.items.length) % this.items.length;
this.show();
}
show() {
console.log(`Showing: ${this.items[this.#currentIndex]}`);
}
getCurrentIndex() {
return this.#currentIndex;
}
}
const carousel = new Carousel(["img1.jpg", "img2.jpg", "img3.jpg"]);
carousel.next(); // Showing: img2.jpg
console.log(carousel.getCurrentIndex()); // 1
// carousel.#currentIndex ❌ (Error: private field)
The carousel’s internal state (#currentIndex
) is private. Consumers can only change slides via next()
/ prev()
and can’t tamper with the internals directly.
Encapsulation protects internal state and provides a clear, safe API for interaction. This reduces bugs and makes codebases easier to evolve without breaking consumers.
Bringing It All Together
Let’s combine what we’ve learned so far: classes, constructors, inheritance, and polymorphism into one example.
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, I’m ${this.name}`);
}
}
class Admin extends User {
greet() { // method overriding (polymorphism)
console.log(`👋 Admin ${this.name} here. I manage the system.`);
}
}
const normalUser = new User("Alice");
const adminUser = new Admin("Bob");
normalUser.greet(); // Hello, I’m Alice
adminUser.greet(); // 👋 Admin Bob here. I manage the system.
Real world example
If you’ve worked with React, you’ve already used OOP principles. Before hooks become popular, we were using OOPs to make class based components. If you are someone who is working in building libraries or polyfills then you will be using OOPs a lot.
import React from "react";
// Base Component
class Button extends React.Component {
render() {
return <button>{this.props.label}</button>;
}
}
// Specialized Component (Inheritance + Polymorphism)
class IconButton extends Button {
render() {
return (
<button>
<span role="img" aria-label="icon">
🔍
</span>{" "}
{this.props.label}
</button>
);
}
}
export default function App() {
return (
<div>
<Button label="Click Me" />
<IconButton label="Search" />
</div>
);
}
Summary
OOPs in JavaScript are important to write clean and maintainable code. OOPs also promotes Solid prinicples of programming.
OOP concept | What/Why |
---|---|
Object | A collection of related data (properties) and behavior (methods). Organizes code into real-world entities. Promotes Single Responsiblity Model (SRE) and Keep it simple silly (KISS) |
Class & Constructor | A blueprint for creating objects. The constructor sets initial state when creating an instance.Provides structure, consistency, and easier instantiation of objects. |
Inheritance | A child class reuses and extends functionality from a parent class. Promotes DRY principle, code reuse, and logical hierarchies. |
Polymorphism | Child classes override or redefine parent methods with their own behavior. Flexibility: same method name, different outcomes depending on context.Promotes Open/Closed Principle |
Abstraction | Hides implementation details and exposes only required functionality. Simulated in JS via base classes that throw errors. Defines clear contracts while allowing flexible implementations. Promotes KISS, and Interface Segregation Principle |
Encapsulation | Restricting direct access to internal state; exposing it through public methods.Improves maintainability, security, and prevents unintended interference. Promotes SRP, Low Coupling / High Cohesion |
I hope this help you to write your clean, and better code in your next project. If you like it then do share to your social media - X, Linkedin.
Top comments (0)