# Object-Oriented Programming in JavaScript

If you have been writing JavaScript for a while, you have probably gotten comfortable with variables, functions, arrays, and maybe even some array methods. That is a solid foundation. But at some point, you start writing code that feels repetitive. You find yourself creating the same kind of object over and over, copy-pasting properties, and wondering if there is a cleaner way to organize everything.

That is exactly where Object-Oriented Programming comes in.

This guide will walk you through what OOP actually means, how JavaScript handles it with classes, and how you can use these ideas to write code that is cleaner, more organized, and far easier to reuse.

* * *

## TL;DR

OOP is a way of organizing code around objects. A class is a blueprint, and objects are what you build from it. You define a class once with a constructor and some methods, then create as many objects as you need from it. Each object gets its own data but shares the same structure and behavior. Classes vs plain objects, default constructor values, and what the `new` keyword actually does are all covered below.

* * *

## What is Object-Oriented Programming?

Object-Oriented Programming, or OOP, is a way of thinking about and structuring your code. Instead of writing a series of instructions that run top to bottom, you organize your code around objects. Each object has its own data and its own behavior.

Think of it like building with LEGO blocks. You define what a block looks like and what it can do, and then you can create as many of those blocks as you need. Each one is independent, but they all follow the same design.

The core idea behind OOP is that related data and the functions that work on that data should live together. Instead of having a name variable here, an age variable there, and a function floating somewhere else, you bundle all of it into one neat package called an object.

* * *

## The Blueprint Analogy

Here is an analogy that makes this click for most people.

Imagine an architect drawing up a blueprint for a house. The blueprint describes the house: how many rooms it has, where the windows go, what the layout looks like. But the blueprint itself is not a house. You cannot live in a blueprint.

When a construction crew follows that blueprint and actually builds something, that is when you get a real house. You can build multiple houses from the same blueprint, and each one will have the same structure but can have different details, like different paint colors or furniture.

In OOP:

*   The **blueprint** is called a **class**
    
*   The actual **house** that gets built is called an **object** (or an instance)
    

You write a class once, and then you can create as many objects from it as you want. Each object gets its own copy of the data, but they all share the same structure and behavior.

* * *

## What is a Class in JavaScript?

A class in JavaScript is essentially a template for creating objects. You define it once, and it describes what properties an object will have and what it can do.

Here is the simplest possible class:

```javascript
class Car {
  // this is the blueprint for a car
}
```

That is a valid class. It does not do much yet, but it is a start. Now let us make it actually useful.

* * *

## Class vs Plain Object: What is the Difference?

Before going further, it is worth pausing on a question beginners often have: "I already know how to make objects in JavaScript. Why do I need a class?"

You are right that JavaScript lets you create objects without any class at all:

```javascript
// plain object literal
const person = {
  name: "Alice",
  age: 25,
  greet() {
    return `Hi, I am ${this.name}.`;
  }
};

console.log(person.greet()); // Hi, I am Alice.
```

That works fine for one object. But what happens when you need ten people? Or a hundred?

```javascript
// you have to repeat yourself every single time
const person1 = { name: "Alice", age: 25, greet() { return `Hi, I am ${this.name}.`; } };
const person2 = { name: "Bob",   age: 30, greet() { return `Hi, I am ${this.name}.`; } };
const person3 = { name: "Carol", age: 22, greet() { return `Hi, I am ${this.name}.`; } };
```

The `greet` method is copy-pasted across every object. If you want to change how it works, you have to update every single one manually. That scales terribly.

A class solves this by defining the structure and behavior once:

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

  greet() {
    return `Hi, I am ${this.name}.`;
  }
}

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);
const person3 = new Person("Carol", 22);

console.log(person1.greet()); // Hi, I am Alice.
console.log(person2.greet()); // Hi, I am Bob.
```

One `greet` method, shared across every object. Change it in the class and every instance gets the update automatically. That is the real argument for classes over plain object literals.

* * *

## The Constructor Method

Every class has a special method called `constructor`. This runs automatically whenever you create a new object from the class. It is where you set up the initial data for the object.

```javascript
class Car {
  constructor(brand, model, year) {
    this.brand = brand;
    this.model = model;
    this.year = year;
  }
}
```

The `this` keyword refers to the specific object being created. So when you say `this.brand = brand`, you are saying "give this particular object a brand property and set it to whatever was passed in."

Now let us actually create some objects from this class:

```javascript
const car1 = new Car("Toyota", "Camry", 2021);
const car2 = new Car("Honda", "Civic", 2023);

console.log(car1.brand); // Toyota
console.log(car2.model); // Civic
```

Notice the `new` keyword. That is how you tell JavaScript to create a new object from a class. Each object gets its own data. Changing something on `car1` does not affect `car2` at all.

* * *

## Default Values in the Constructor

Here is something that will save you some headaches early on. What happens if someone creates an object but forgets to pass an argument?

```javascript
const car3 = new Car("BMW");

console.log(car3.model); // undefined
console.log(car3.year);  // undefined
```

Not great. JavaScript does not throw an error, it just fills in `undefined` for anything that was not provided. You can prevent this by setting default values directly in the constructor parameters:

```javascript
class Car {
  constructor(brand = "Unknown", model = "Unknown", year = 2020) {
    this.brand = brand;
    this.model = model;
    this.year = year;
  }

  describe() {
    return `${this.year} ${this.brand} ${this.model}`;
  }
}

const car1 = new Car("Toyota", "Camry", 2021);
const car2 = new Car("Honda");  // model and year will use defaults
const car3 = new Car();         // everything uses defaults

console.log(car1.describe()); // 2021 Toyota Camry
console.log(car2.describe()); // 2020 Honda Unknown
console.log(car3.describe()); // 2020 Unknown Unknown
```

Default values are a simple way to make your class more resilient. The object still gets created cleanly, and you can always update the properties later if needed. Use them any time a property has a sensible fallback.

* * *

## Adding Methods to a Class

Properties store data. Methods are functions that belong to the class and define what an object can do.

Let us add a method to the `Car` class:

```javascript
class Car {
  constructor(brand, model, year) {
    this.brand = brand;
    this.model = model;
    this.year = year;
  }

  describe() {
    return `${this.year} ${this.brand} ${this.model}`;
  }

  startEngine() {
    return `${this.brand} engine is running...`;
  }
}

const myCar = new Car("Ford", "Mustang", 2022);

console.log(myCar.describe());     // 2022 Ford Mustang
console.log(myCar.startEngine());  // Ford engine is running...
```

Every object you create from `Car` automatically has access to `describe()` and `startEngine()`. You write the method once and it works for every instance. That is the power of reusability.

* * *

## A More Relatable Example: The Person Class

Let us try another example to make this even more concrete:

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

  greet() {
    return `Hi, my name is ${this.name} and I am ${this.age} years old.`;
  }

  isAdult() {
    return this.age >= 18;
  }
}

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 15);

console.log(person1.greet());    // Hi, my name is Alice and I am 25 years old.
console.log(person1.isAdult());  // true

console.log(person2.greet());    // Hi, my name is Bob and I am 15 years old.
console.log(person2.isAdult());  // false
```

Two objects, same class, completely independent data. `person1` and `person2` each have their own `name` and `age`, and calling `isAdult()` on each gives a different result based on their own data.

* * *

## Code Reusability: Why This Actually Matters

Imagine you needed to work with 50 students in a school application. Without classes, you might do something like this:

```javascript
// without classes - repetitive and hard to manage
const student1 = { name: "Alice", age: 20, grade: "A" };
const student2 = { name: "Bob", age: 22, grade: "B" };
// ... 48 more of these
```

There is no shared structure, no shared behavior, and if you want to add a method that works on each student, you have to add it to every single object manually. That is a nightmare.

With a class, you define the structure once:

```javascript
class Student {
  constructor(name, age, grade) {
    this.name = name;
    this.age = age;
    this.grade = grade;
  }

  introduce() {
    return `I am ${this.name}, ${this.age} years old, with a grade of ${this.grade}.`;
  }
}

const student1 = new Student("Alice", 20, "A");
const student2 = new Student("Bob", 22, "B");
const student3 = new Student("Charlie", 19, "A+");

console.log(student1.introduce()); // I am Alice, 20 years old, with a grade of A.
console.log(student2.introduce()); // I am Bob, 22 years old, with a grade of B.
console.log(student3.introduce()); // I am Charlie, 19 years old, with a grade of A+.
```

Clean. Consistent. And if you want to change how `introduce()` works, you change it in one place and it updates for all objects automatically.

* * *

## A Quick Word on Encapsulation

Encapsulation is one of the core ideas in OOP, and the basic version of it is simpler than it sounds.

The idea is that an object should be responsible for its own data. Instead of reaching into an object and changing a value directly from outside, you interact with it through methods.

Here is a simple example:

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

  deposit(amount) {
    if (amount > 0) {
      this.balance += amount;
      return `Deposited ${amount}. New balance: ${this.balance}`;
    }
    return "Invalid deposit amount.";
  }

  withdraw(amount) {
    if (amount > this.balance) {
      return "Insufficient funds.";
    }
    this.balance -= amount;
    return `Withdrew ${amount}. Remaining balance: ${this.balance}`;
  }

  getBalance() {
    return `Current balance: ${this.balance}`;
  }
}

const account = new BankAccount("Alice", 1000);

console.log(account.deposit(500));   // Deposited 500. New balance: 1500
console.log(account.withdraw(200));  // Withdrew 200. Remaining balance: 1300
console.log(account.getBalance());   // Current balance: 1300
```

Notice that instead of just doing `account.balance = 5000` from outside (which anyone could do), the class controls how the balance changes through its own methods. The deposit method checks if the amount is valid. The withdraw method checks if there are enough funds.

The object manages its own state. That is the spirit of encapsulation, even at this basic level.

* * *

## Putting It All Together

Here is a slightly bigger example that combines everything you have learned so far:

```javascript
class Animal {
  constructor(name, species, sound) {
    this.name = name;
    this.species = species;
    this.sound = sound;
  }

  speak() {
    return `${this.name} says ${this.sound}!`;
  }

  getInfo() {
    return `${this.name} is a ${this.species}.`;
  }
}

const dog = new Animal("Rex", "Dog", "Woof");
const cat = new Animal("Whiskers", "Cat", "Meow");
const cow = new Animal("Bessie", "Cow", "Moo");

const animals = [dog, cat, cow];

animals.forEach(animal => {
  console.log(animal.getInfo());
  console.log(animal.speak());
  console.log("---");
});

// Rex is a Dog.
// Rex says Woof!
// ---
// Whiskers is a Cat.
// Whiskers says Meow!
// ---
// Bessie is a Cow.
// Bessie says Moo!
// ---
```

You created three different objects, stored them in an array, and looped through them calling the same methods on each. The code is clean, predictable, and easy to extend. If you wanted to add a `Lion` or a `Parrot`, you just create a new object. No new functions needed, no new code structure. The class handles everything.

* * *

## What Happens If You Forget `new`?

This is something every beginner runs into at least once. You write a class, try to create an object, and something goes wrong in a way that is not immediately obvious.

The `new` keyword is not optional. It is what tells JavaScript to create a fresh object, run the constructor on it, and hand it back to you. If you forget it, JavaScript tries to call the class like a regular function and throws an error:

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

  greet() {
    return `Hi, I am ${this.name}.`;
  }
}

// correct
const person1 = new Person("Alice", 25);
console.log(person1.greet()); // Hi, I am Alice.

// forgetting new
const person2 = Person("Bob", 30); // TypeError: Cannot call a class as a function
```

JavaScript classes are intentionally strict about this. Unlike the older constructor function pattern, classes will always throw a `TypeError` if you call them without `new`. That is actually a good thing, because at least the error is clear rather than silently producing broken behavior.

The fix is always the same: just add `new` in front of the class name. If you see `TypeError: Cannot call a class as a function`, that is the first place to look.

* * *

## Your Challenge

Now it is your turn to practice. Here is what to build:

**Create a class called** `Student` with the following:

*   A `constructor` that accepts `name`, `age`, and `subject`
    
*   A method called `getDetails()` that returns a string like: "Name: Alice | Age: 20 | Subject: Math"
    
*   A method called `study()` that returns something like: "Alice is studying Math."
    

Once the class is ready:

1.  Create at least three different `Student` objects with different data
    
2.  Call both methods on each object and log the results to the console
    

**Bonus challenge:** Add a method called `isEligible()` that returns `true` if the student's age is 18 or older, and `false` otherwise. Test it on a few different students.

This might feel straightforward, but actually writing it yourself is what makes it stick. Give it a go before looking anything up.

* * *

## Wrapping It Up

Classes give you a clean way to create objects, bundle data with behavior, and stop repeating yourself. Once you are comfortable writing a class, adding a constructor, and calling methods on instances, you have the foundation that everything else in OOP builds on.

The next natural steps from here are inheritance, private fields, and static methods, but there is no rush. Write a few classes of your own first. That hands-on time is what makes it click.
