# OOP Basics

Course: [Low-Level Design](https://www.devtutorials.ai/learn/system-design/lld) · Section: LLD Foundations
Source: https://www.devtutorials.ai/learn/system-design/lld/foundations/oop-basics

> Learn classes and objects, then the four big ideas of object-oriented code: encapsulation, abstraction, inheritance and polymorphism, with QuickBite examples you can run.

Look at a kitchen. You can see a fridge, a stove, and a mixer. Each one stores its own things inside and has a few buttons on the outside. You press "start" on the mixer. You do not open it and run the motor yourself.

Object-oriented programming works in a similar way. A program is made up of small parts called **objects**. Each object stores its own data and has a few buttons, called **methods**, on the outside.

**In simple words:** object-oriented programming (OOP) means building a program using small parts that take care of themselves.

This lesson explains the basics. The next lesson, SOLID, builds on all the ideas you learn here.

## What You Will Learn

In this lesson, you will learn:

1. What a class and an object are.
2. Encapsulation: keeping data safe inside a class.
3. Abstraction: showing what a class does while hiding how it works.
4. Inheritance: creating a new class from an existing class.
5. Polymorphism: using different classes in the same way.

The names in the pictures and the exercises in this lesson use TypeScript, because it runs directly in your browser. If you know Java or C#, the code will look familiar. The ideas are the same in any programming language that supports classes.

## Classes and Objects

A **class** is a blueprint or plan. An **object** is a real thing created from that plan.

Think about a cookie cutter. You create the cutter once, and then you can make many cookies with it. Each cookie can have its own icing.

In programming, the class is the cookie cutter. Each object is a cookie. You create an object using the word `new`.

### Your First Class

Here is a class that represents a dish on the QuickBite menu. It has two pieces of data: a name and a price. It also has one method called `label()`. Step through what happens when QuickBite makes two dishes from this one class:

Here is the same idea in code:

```ts runnable title="One class, two objects"
class Dish {
  name: string;
  price: number;

  constructor(name: string, price: number) {
    this.name = name;
    this.price = price;
  }

  label(): string {
    return `${this.name} - Rs ${this.price}`;
  }
}

const dosa = new Dish("Masala dosa", 120);
const lassi = new Dish("Mango lassi", 80);

console.log(dosa.label());
console.log(lassi.label());
```

Expected output:

```text
Masala dosa - Rs 120
Mango lassi - Rs 80
```

There is one class and two objects. Each object has its own name and price values. `dosa.label()` gives "Masala dosa - Rs 120" and `lassi.label()` gives "Mango lassi - Rs 80".

### Fields, Methods and the Constructor

A class has three main parts:

- **Fields** store data, such as `name` and `price`.
- **Methods** perform actions, such as `label()`.
- The **constructor** runs once when you create an object using `new`. It sets the values of the fields.

The word `this` refers to the current object. So `this.name` means the name of this dish object, not the name of any other dish.

## The Four Big Ideas

Four important ideas make classes useful. Each idea helps solve a different problem.

| Idea | In simple words | QuickBite example |
|---|---|---|
| Encapsulation | Keep data protected inside the class | A wallet balance that cannot go below zero |
| Abstraction | Show what it does and hide how it works | `pay(amount)`, without knowing which bank is used |
| Inheritance | A new class reuses an existing class | `ExpressDelivery` builds on `Delivery` |
| Polymorphism | Different classes can be used in the same way | Card, UPI and cash all use `pay(amount)` |

## Encapsulation: Protect the Data

A bank does not allow you to walk into the vault and change your balance yourself. Instead, you go to the counter, where the staff checks the rules before making any changes.

Encapsulation works in the same way. A class keeps its data **private**. Other code must use the class's methods to work with the data, and those methods make sure the rules are followed.

### The Wallet Example

The `Wallet` class has a `private balance` field and two methods, `add(amount)` and `spend(amount)`. The rule "you cannot spend more than you have" lives inside `spend()`. Watch two calls on a wallet that holds 300:

The code behind the animation:

```ts runnable title="Private data can only change through the class's own rules"
class Wallet {
  private balance = 0;

  add(amount: number): void {
    if (amount <= 0) throw new Error("amount must be positive");
    this.balance += amount;
  }

  spend(amount: number): boolean {
    if (amount > this.balance) return false; // the rule lives here, once
    this.balance -= amount;
    return true;
  }

  get current(): number {
    return this.balance;
  }
}

const w = new Wallet();
w.add(300);
console.log(w.spend(500), w.current);
console.log(w.spend(120), w.current);
```

Expected output:

```text
false 300
true 180
```

Here is what happened:

- The wallet started with a balance of 300.
- Spending 500 failed because there was not enough money in the wallet. `spend` returned false and the balance remained 300.
- Spending 120 was successful. `spend` returned true and the balance became 180.

### Why It Matters

If other code tries to write `w.balance = -1000`, TypeScript shows an error. This happens because the field is marked as `private`.

Without encapsulation, any part of the code could break the rule. You would then need to search through the entire application to find where the problem happened.

A public field can be changed by any code. This makes it easier for bugs to occur. Keep fields `private` unless you have a good reason to make them public.

## Abstraction: Show What, Hide How

When you drive a car, you press the brake pedal. You do not need to know how the brake system works internally.

Abstraction works the same way in programming. A class provides a few simple methods to use, while the internal steps remain hidden.

### The Payment Example

The checkout page only needs to call `pay(amount)`. Finding the bank and writing logs are hidden helper methods. Follow one payment of 450 through the `PaymentGateway` class:

Here is the same idea in code:

```ts runnable title="The caller sees one method; the steps stay hidden"
class PaymentGateway {
  // The one method other code is allowed to call.
  pay(amount: number): string {
    const bank = this.findBank();
    this.writeLog(`sending ${amount} to ${bank}`);
    return `paid ${amount}`;
  }

  // Hidden helpers. Other code cannot call these.
  private findBank(): string {
    return "QuickBank";
  }
  private writeLog(line: string): void {
    this.log.push(line);
  }
  private log: string[] = [];

  get lastLog(): string {
    return this.log[this.log.length - 1];
  }
}

const gateway = new PaymentGateway();
console.log(gateway.pay(450));
console.log(gateway.lastLog);
```

Expected output:

```text
paid 450
sending 450 to QuickBank
```

The caller uses only one line: `gateway.pay(450)`, and gets back "paid 450". It does not call `findBank()` or `writeLog()`. Since those methods are `private`, they cannot be accessed from outside the class.

### Why It Matters

In the future, QuickBite might change to a different bank. In that case, only the hidden implementation needs to be updated. The checkout page can continue calling `pay(amount)` without any changes.

Without abstraction, every caller would need to know the banking steps. If the bank changes, all those callers would also need to be updated.

## Inheritance: Build on an Existing Class

A child often shares many characteristics with a parent, but it can also have some differences.

Inheritance allows a new class to reuse everything from an existing class. The new class is called the **child** (or subclass), and the existing class is called the **parent** (or superclass). The child class can add new features or change existing behavior.

### The Delivery Example

An express delivery is similar to a normal delivery, but it costs 30 more. So `ExpressDelivery` **extends** `Delivery` and changes only the `fee()` method. Follow one call to `summary()` on a 4 km express delivery:

The code behind the animation:

```ts runnable title="ExpressDelivery reuses Delivery and changes one thing"
class Delivery {
  constructor(protected readonly distanceKm: number) {}

  fee(): number {
    return 30;
  }

  summary(): string {
    return `${this.distanceKm} km, fee ${this.fee()}`;
  }
}

class ExpressDelivery extends Delivery {
  // Only the fee is different. summary() is reused as it is.
  fee(): number {
    return super.fee() + 30;
  }
}

console.log(new Delivery(4).summary());
console.log(new ExpressDelivery(4).summary());
```

Expected output:

```text
4 km, fee 30
4 km, fee 60
```

A normal 4 km delivery gives "4 km, fee 30". The express one gives "4 km, fee 60". `ExpressDelivery` did not copy the `summary()` method. It inherited it from `Delivery` automatically. The word `super` refers to the parent's version of a method, so `super.fee()` returns 30.

### Use It With Care

Use inheritance only when the child class truly **is a type of** the parent class. An express delivery is a type of delivery, so inheritance makes sense here.

If the child class needs to change most of the parent's methods, inheritance is probably not the right choice. You will see an example of this in the SOLID lesson.

## Polymorphism: Same Call, Different Results

Think about a TV remote. The power button works on both a Sony TV and a Samsung TV. You press the same button, but each TV responds in its own way.

In programming, an **interface** works like that remote. It defines the methods a class must provide, but it does not define how those methods should work. Multiple classes can follow the same interface and implement the methods in their own way.

### The Payment Example

The `PaymentMethod` interface has one method: `pay(amount)`. The classes `Card`, `Upi` and `Cash` all follow it. A loop goes through a list of one Card, one Upi and one Cash object and calls `pay(450)` on each. Step through it:

Here is the same idea in code:

```ts runnable title="One call, three behaviours"
interface PaymentMethod {
  pay(amount: number): string;
}

class Card implements PaymentMethod {
  pay(amount: number): string { return `card charged ${amount}`; }
}
class Upi implements PaymentMethod {
  pay(amount: number): string { return `upi request ${amount}`; }
}
class Cash implements PaymentMethod {
  pay(amount: number): string { return `collect ${amount} in cash`; }
}

const methods: PaymentMethod[] = [new Card(), new Upi(), new Cash()];
for (const m of methods) console.log(m.pay(450));
```

Expected output:

```text
card charged 450
upi request 450
collect 450 in cash
```

The loop calls `pay(450)` three times and gets "card charged 450", "upi request 450" and "collect 450 in cash". It does not check which payment method it is using. Each object responds in its own way.

### Why It Matters

If you want to add a new payment method, you only need to create a new class. The loop does not need any changes.

**In simple words:** Polymorphism allows you to work with different objects in the same way.

**Easy way to remember the four ideas:** Encapsulation protects it. Abstraction hides it. Inheritance reuses it. Polymorphism replaces it. Think of "EAIP": protect, hide, reuse, replace.

## Predict the Output

Here is a polymorphism question in C#. Read the code and try to predict the output before running it.

<CsPredict id="poly-1" code={"Payment[] payments = { new Card(), new Upi(), new Card() };\nforeach (var p in payments)\n    Console.WriteLine(p.Pay(100));\n\nabstract class Payment\n{\n    public abstract string Pay(int amount);\n}\n\nclass Card : Payment\n{\n    public override string Pay(int amount) => \"card \" + amount;\n}\n\nclass Upi : Payment\n{\n    public override string Pay(int amount) => \"upi \" + (amount - 5);\n}"} expected={"card 100\nupi 95\ncard 100"} why="Each item is used in the same way through Payment. However, every object runs its own version of the Pay method. Card prints the full amount, while Upi subtracts 5. This is an example of polymorphism." />

## Common Mistakes

| Mistake | Wrong | Correct | Why it happens |
|---|---|---|---|
| Public fields | `balance` can be changed from anywhere. | Use `private balance` and update it through methods. | Making fields public may seem easier because it requires less code. |
| Rules in many places | Every caller checks if there is enough money. | Keep the check in one place, inside `spend()`. | It can feel faster to add the check wherever it is needed. |
| Showing the inside steps | Callers use `findBank()` and `writeLog()` directly. | Callers should use only `pay(amount)`. | In many languages, making everything public is the default behaviour. |
| Inheritance to borrow code | `Rider extends Restaurant` just to reuse an address field. | Use inheritance only when something **is a kind of** something else. | Inheritance often looks like the easiest way to reuse code. |
| Type checks before every call | `if (m instanceof Card) ... else if (m instanceof Upi) ...` | Call `m.pay(amount)` and let each class handle its own behavior. | Beginners may not realize that interfaces can handle this automatically. |

## Practice

## What to Remember

- A **class** is a blueprint or plan. An **object** is an actual thing created from that plan.
- **Encapsulation:** keep data private and update it through methods.
- **Abstraction:** provide simple methods and hide the internal details.
- **Inheritance:** a child class reuses a parent class. Use it only when it represents an "is a kind of" relationship.
- **Polymorphism:** use one method call, and each class responds in its own way.

## Further Reading

These are the sources used to check this lesson:

- [TypeScript Handbook: Classes](https://www.typescriptlang.org/docs/handbook/2/classes.html): classes, private fields, inheritance and interfaces.
- [Microsoft Learn: Object-oriented programming in C#](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/oop): the four ideas with C# examples.
- [Microsoft Learn: Polymorphism in C#](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/polymorphism): virtual and override methods.

Next: **SOLID Principles**. You will use these four ideas to fix a real QuickBite class.
