OOP & Classes in JavaScript
OOP stands for Object-Oriented Programming. It is a way of organising your code around things (objects) rather than just a list of actions or steps.
Instead of writing a series of disconnected functions and variables, OOP groups related data and behaviour together into neat bundles called objects. This makes your code easier to read, easier to maintain, and easier to reuse.
Think of it like this: imagine you are building a car factory. Without OOP, you might have a hundred separate variables — car1Colour, car1Speed, car2Colour, car2Speed — and a hundred separate functions that have no obvious connection to each other. With OOP, you create a Car blueprint, and every car you make follows that blueprint. All the car's data and actions live together in one place.
Real-world analogy: Think of a car blueprint (or architectural plan). The blueprint defines what a car looks like and what it can do. You can use that same blueprint to build hundreds of identical cars. In OOP, that blueprint is called a class, and each car you build from it is called an object.
// Without OOP — scattered variables and functions
let car1Colour = "red";
let car1Speed = 0;
function accelerateCar1() {
car1Speed += 10;
}
// With OOP — everything grouped neatly in a class
class Car {
constructor(colour) {
this.colour = colour;
this.speed = 0;
}
accelerate() {
this.speed += 10;
}
}
const myCar = new Car("red");
myCar.accelerate();
console.log(myCar.speed); // 10
OOP is built on four core pillars: Encapsulation (bundling data and methods together), Abstraction (hiding complex details), Inheritance (one class borrowing from another), and Polymorphism (objects behaving differently depending on context). We will explore these ideas as we go through the sections below.
A class is like a blueprint or a template. It describes what an object should look like and what it can do — but it is not an object itself. You use a class to create objects.
Real-world analogy: Imagine a cookie cutter. The cutter itself is not a cookie. But you can use it to stamp out as many cookies as you like, and every cookie will have the same shape. The cookie cutter is the class, and each cookie is an object (also called an instance).
In JavaScript, you define a class using the class keyword, followed by the name of the class (by convention, class names start with a capital letter).
// Define a class (the blueprint / cookie cutter)
class Person {
// We will add details inside here
}
// Create objects (instances) from the class
const person1 = new Person();
const person2 = new Person();
console.log(person1); // Person {}
console.log(person2); // Person {}
// Each object is a separate instance
console.log(person1 === person2); // false
Right now the class is empty — it does not hold any data yet. In the next sections we will add a constructor, properties, and methods to bring it to life. Just remember: the new keyword is what creates a real object from the class blueprint.
Classes were introduced in ES6 (2015). Before that, developers had to use constructor functions and prototypes to achieve the same result. Classes are a cleaner, more readable way of doing the same thing under the hood.
The constructor is a special method inside a class. It runs automatically the moment you create a new object using the new keyword. You use it to set up the initial data for the object.
Real-world analogy: Think about when a new employee joins a company. On their very first day, they go through onboarding — they get their laptop, they set up their email, they are told their role and their manager. That onboarding process happens automatically when they join. The constructor is exactly that: the automatic setup that happens when a new object is created.
Inside the constructor, this refers to the specific object being created. So this.name means "store the name on this object".
class Person {
constructor(name, age) {
// Runs automatically when a new Person is created
this.name = name;
this.age = age;
}
}
// The values we pass in go straight into the constructor
const alice = new Person("Alice", 30);
const bob = new Person("Bob", 25);
console.log(alice.name); // Alice
console.log(alice.age); // 30
console.log(bob.name); // Bob
console.log(bob.age); // 25
Every class can only have one constructor. If you do not write one, JavaScript quietly provides an empty constructor behind the scenes. But as soon as your objects need any data on creation, you will want to write your own.
Every object has two kinds of things: properties and methods.
- Properties are the data stored on an object — like its name, age, or colour. They describe what the object is.
- Methods are functions attached to an object — like drive, bark, or greet. They describe what the object can do.
Real-world analogy: Think of a dog. A dog has properties: a name, a breed, a colour, and an age. A dog also has methods: it can bark, sit, fetch a ball, and wag its tail. Properties are facts about the dog; methods are actions the dog can perform.
class Dog {
constructor(name, breed, colour) {
// Properties — data about this dog
this.name = name;
this.breed = breed;
this.colour = colour;
}
// Method — something the dog can do
bark() {
console.log(`${this.name} says: Woof!`);
}
// Method — another action
fetch(item) {
console.log(`${this.name} fetches the ${item}!`);
}
// Method — introduce the dog
describe() {
console.log(`${this.name} is a ${this.colour} ${this.breed}.`);
}
}
const rex = new Dog("Rex", "Labrador", "golden");
// Accessing properties
console.log(rex.name); // Rex
console.log(rex.breed); // Labrador
// Calling methods
rex.bark(); // Rex says: Woof!
rex.fetch("tennis ball"); // Rex fetches the tennis ball!
rex.describe(); // Rex is a golden Labrador.
Notice how the methods use this.name to access the dog's own data. Inside a method, this always refers to the specific object that called it. So rex.bark() knows to print "Rex", not some other dog's name.
You can add as many properties and methods as you need. That is the beauty of classes — they keep everything that belongs to a "thing" neatly packaged together.
Inheritance lets one class borrow features — properties and methods — from another class. The class that shares its features is called the parent class (or superclass). The class that borrows them is the child class (or subclass).
This is incredibly useful when you have shared behaviour. Instead of writing the same code twice, you write it once in the parent class and let the child class inherit it automatically.
Real-world analogy: A child inherits traits from their parents — perhaps the same eye colour, the same sense of humour, or a natural talent for music. But the child also has their own unique traits that the parents do not have. Inheritance in OOP works the same way: the child class gets everything the parent has, plus it can add or override things as needed.
In JavaScript, you use the extends keyword to create a child class. Inside the child's constructor, you must call super() — this calls the parent's constructor and sets up the inherited properties.
// Parent class
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
console.log(`${this.name} says: ${this.sound}!`);
}
eat() {
console.log(`${this.name} is eating.`);
}
}
// Child class — inherits from Animal
class Dog extends Animal {
constructor(name) {
// super() calls the Animal constructor
super(name, "Woof");
this.tricks = []; // Dog-specific property
}
// Dog-specific method — not in Animal
learnTrick(trick) {
this.tricks.push(trick);
console.log(`${this.name} learned: ${trick}`);
}
}
// Another child class
class Cat extends Animal {
constructor(name) {
super(name, "Meow");
}
// Cat overrides the speak method
speak() {
console.log(`${this.name} purrs and says: ${this.sound}...`);
}
}
const rex = new Dog("Rex");
rex.speak(); // Rex says: Woof! (inherited from Animal)
rex.eat(); // Rex is eating. (inherited from Animal)
rex.learnTrick("sit"); // Rex learned: sit (Dog-only method)
const whiskers = new Cat("Whiskers");
whiskers.speak(); // Whiskers purrs and says: Meow... (overridden)
Notice that Cat overrides the speak method. When a child class defines a method with the same name as the parent, it replaces the parent version for that specific child. This is called method overriding.
Inheritance helps you avoid repeating yourself (the DRY principle — Don't Repeat Yourself). Define shared behaviour once in the parent, and every child class gets it for free.
Static methods belong to the class itself, not to any individual object created from that class. You cannot call a static method on an instance — you call it directly on the class.
Real-world analogy: Imagine a factory that makes phones. The factory has a general contact number — 0800-FACTORY. That number belongs to the factory itself, not to any one phone it produced. You would not try to call the factory by dialling a phone it made. The factory's number is like a static method: it belongs to the class (the factory), not to any object (phone) it produces.
You define a static method with the static keyword before the method name. Static methods are great for utility functions that are related to the class but do not need access to an individual object's data.
class MathHelper {
// Static method — belongs to the class, not an instance
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static square(n) {
return n * n;
}
}
// Call directly on the class — NO 'new' needed
console.log(MathHelper.add(5, 3)); // 8
console.log(MathHelper.multiply(4, 6)); // 24
console.log(MathHelper.square(7)); // 49
// This would throw an error:
// const helper = new MathHelper();
// helper.add(1, 2); // TypeError: helper.add is not a function
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// Static method: useful for validation before creating an instance
static isValidEmail(email) {
return email.includes("@") && email.includes(".");
}
}
console.log(User.isValidEmail("alice@example.com")); // true
console.log(User.isValidEmail("not-an-email")); // false
You will see static methods used a lot in utility classes and helper libraries. JavaScript's built-in Math object is a great real example — Math.round(), Math.max(), and Math.floor() are all static methods that you call on the class directly.
Getters and setters give you control over how you read and write an object's properties. Instead of directly accessing a property, you can add logic in between — like validation, formatting, or calculation.
- A getter lets you retrieve a value — it looks like a property but runs like a function.
- A setter lets you control how a value is set — you can validate or transform the incoming value before storing it.
Real-world analogy: Think of a bank account. You can check your balance at any time — that's a getter. But to withdraw money, you have to go through a process: the bank checks whether you have enough funds before allowing it. That controlled process is a setter. It is not a simple assignment — there are rules involved.
In JavaScript, you define them with the get and set keywords. The clever part: from the outside, they look and feel exactly like normal properties — no parentheses needed.
class BankAccount {
constructor(owner, balance) {
this.owner = owner;
this._balance = balance; // Underscore signals "private by convention"
}
// Getter — read the balance
get balance() {
return `£${this._balance.toFixed(2)}`;
}
// Setter — control how balance is changed
set balance(amount) {
if (amount < 0) {
console.log("Error: Balance cannot go below zero.");
return;
}
this._balance = amount;
}
}
const account = new BankAccount("Alice", 1000);
// Using the getter — looks like a property, no ()
console.log(account.balance); // £1000.00
// Using the setter — looks like a normal assignment
account.balance = 500;
console.log(account.balance); // £500.00
// Setter validation kicks in
account.balance = -100; // Error: Balance cannot go below zero.
console.log(account.balance); // £500.00 (unchanged)
The underscore prefix on _balance is a convention (not enforced by JavaScript) to signal that it is an internal property that should not be accessed directly from outside the class. The getter and setter act as a controlled interface to it.
Getters and setters are a key part of encapsulation — one of the four pillars of OOP. They let you keep the internal workings of an object hidden while still allowing safe access from outside.
Here is a quick recap of everything covered in this tutorial:
- OOP is a programming style that organises code around objects (things) rather than just actions. It is built on four pillars: Encapsulation, Abstraction, Inheritance, and Polymorphism.
- A class is a blueprint or template. You use it to create objects (instances). Class names start with a capital letter by convention.
- The constructor is a special method that runs automatically when a new object is created. It sets up the initial data using this.
- Properties are data stored on an object (like this.name). Methods are functions attached to an object (like bark()).
- Inheritance uses the extends keyword to let one class borrow features from another. Use super() in the child constructor to call the parent constructor.
- Static methods are defined with the static keyword and belong to the class itself, not to any specific instance. Call them directly on the class.
- Getters (get) let you read a value with logic. Setters (set) let you control how a value is written. They look like properties from the outside.
- Classes were introduced in ES6 (2015) and are syntactic sugar over JavaScript's existing prototype-based system.
OOP can feel overwhelming at first, but it clicks once you start thinking in terms of real-world things. A user, a product, a car, a bank account — all of these can be modelled as classes in your code.
These are common interview questions on OOP and Classes in JavaScript. Try to answer each one out loud in your own words before looking them up:
- What is Object-Oriented Programming (OOP)?
- What are the four pillars of OOP? Explain each one briefly.
- What is the difference between a class and an object?
- What does the constructor method do, and when is it called?
- What is the difference between a property and a method?
- What is inheritance in JavaScript? How do you implement it?
- What are the extends and super keywords used for?
- What is a static method? How is it different from a regular method?
- What are getters and setters? When would you use them?
- What is encapsulation and why is it useful?
- Can a class extend more than one class in JavaScript?
- What is method overriding? Give an example.
- What is the difference between class syntax and constructor functions in JavaScript?
- What does this refer to inside a class method?
- How would you create a private property in a JavaScript class?