When to Use OOP in JavaScript
Use object-oriented programming when it makes the problem easier to model.
JavaScript supports OOP, but it does not force every problem into classes.
Rule of thumb: start with plain objects and functions. Move to classes when state + behavior + rules make the model clearer.
Why the choice matters
Good structure makes code easier to change.
If a class clearly represents a thing in your program, OOP can make the code feel natural.
If a class only wraps a few unrelated functions, it can add noise.
The goal is not to use OOP everywhere. The goal is to choose the shape that makes the code easier to understand.
Use OOP for stateful entities
OOP works well when you have objects that own state and behavior.
class Timer {
constructor() {
this.seconds = 0;
}
tick() {
this.seconds += 1;
}
reset() {
this.seconds = 0;
}
}
const timer = new Timer();
timer.tick();
timer.tick();
console.log(timer.seconds);
The timer owns its state.
Its methods describe what can happen to that state.
Use OOP for repeated objects
Classes are helpful when you need many similar objects.
class Player {
constructor(name) {
this.name = name;
this.score = 0;
}
addPoint() {
this.score += 1;
}
}
const playerOne = new Player("Dirk");
const playerTwo = new Player("Nia");
playerOne.addPoint();
console.log(playerOne.score); // 1
console.log(playerTwo.score); // 0
Each player has separate state.
They share the same behavior.
Use OOP for rules and invariants
OOP is useful when an object has rules that should stay true.
class InventoryItem {
#quantity;
constructor(name, quantity) {
this.name = name;
this.#quantity = quantity;
}
remove(amount) {
if (amount > this.#quantity) {
throw new Error("Not enough inventory");
}
this.#quantity -= amount;
}
get quantity() {
return this.#quantity;
}
}
const item = new InventoryItem("Notebook", 3);
item.remove(1);
console.log(item.quantity); // 2
The class protects its own state.
Outside code cannot directly change #quantity to an invalid value.
Use simpler patterns for simple data
If you only need to group values, a plain object is usually enough.
const profile = {
name: "Dirk",
location: "Portland",
interests: ["JavaScript", "design", "music"],
};
There is no need for a class if the object does not manage behavior or rules.
Use functions for stateless logic
If a piece of logic only turns inputs into outputs, a function may be clearer than a class.
function calculateDiscount(price, percent) {
return price * (percent / 100);
}
console.log(calculateDiscount(50, 10)); // 5
This does not need an object.
It just needs a useful function name.
Be careful with inheritance
Inheritance is powerful, but it can make code rigid when the hierarchy gets too deep.
class Bird {
fly() {
return "Flying";
}
}
class Penguin extends Bird {}
This model has a problem: a penguin is a bird, but it does not fly.
When the real world has exceptions, inheritance can become awkward.
Prefer composition for flexible behavior
Composition means building objects from smaller pieces of behavior.
function logMessage(message) {
console.log(message);
}
class PaymentService {
constructor(logger) {
this.logger = logger;
}
charge(amount) {
this.logger(`Charging $${amount}`);
}
}
const service = new PaymentService(logMessage);
service.charge(25); // Logs "Charging $25"
The service does not need to inherit from a logging class.
It just receives the behavior it needs.
Mixing paradigms
Most JavaScript programs mix objects, functions, arrays, modules, and classes.
That is normal.
You might use a class for a Cart, plain objects for cart items, and functions for formatting prices.
class Cart {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
getTotal() {
return this.items.reduce((total, item) => total + item.price, 0);
}
}
function formatPrice(price) {
return `$${price.toFixed(2)}`;
}
const cart = new Cart();
cart.addItem({ name: "Notebook", price: 8 });
cart.addItem({ name: "Pen", price: 2 });
console.log(formatPrice(cart.getTotal()));
Use each tool where it fits.
Common mistakes
Creating classes for data-only values
If a value is mostly data, a plain object is often clearer than a class.
// Usually fine as a plain object:
const profile = {
name: "Dirk",
location: "Portland",
};
Move to a class when you also have behavior, validation, or invariants that the object should enforce.
Using inheritance only to share a helper
Inheritance should model an is-a relationship, not just "I want a method from somewhere."
class Logger {
log(message) {
console.log(message);
}
}
// Weak model: a service is not a logger.
class OrderService extends Logger {
createOrder(item) {
this.log(`Creating order for ${item}`);
}
}
Prefer composition when one thing just uses another:
class OrderService {
constructor(logger) {
this.logger = logger;
}
createOrder(item) {
this.logger.log(`Creating order for ${item}`);
}
}
Decision checklist
Use OOP when the answer to several of these questions is yes:
- Does this concept have state?
- Does the state need rules?
- Will I create many similar objects?
- Do the methods naturally belong to the data?
- Would a public interface make this easier to use correctly?
Use a simpler pattern when the answer to these questions is no.
If the checklist is mixed, default to the simpler tool first. You can always refactor from a plain object to a class later.
Best practices
Start simple.
Use classes when they make a concept clearer, not just because they are available.
Keep classes focused on one responsibility.
Prefer composition when behavior needs to be mixed and matched.
Avoid deep inheritance trees.
Summary
OOP is useful for stateful concepts, repeated objects, and data with rules.
Plain objects are often better for simple data.
Functions are often better for stateless logic.
Good JavaScript uses the pattern that makes the code easiest to understand.