Foundations

OOP Principles

The four pillars of object-oriented programming — encapsulation, inheritance, polymorphism, and abstraction.

2 min readDesigns · LLD & Patterns

The Four Pillars

PillarOne-line DefinitionMechanism
EncapsulationBundle data and behavior; hide internalsprivate fields + public methods
InheritanceSubclass reuses & specializes a parentextends, implements
PolymorphismOne interface, many implementationsMethod overriding, interfaces
AbstractionExpose what, hide howAbstract classes, interfaces

1. Encapsulation

Bundle state with behavior, and prevent external code from mucking with internals directly.

public class BankAccount {
    private double balance;          // hidden
    private final String accountId;

    public BankAccount(String id, double opening) {
        this.accountId = id;
        this.balance = opening;
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        this.balance += amount;
    }

    public double getBalance() { return balance; }
}

The caller cannot do account.balance = -1000 — they must go through deposit() which enforces the invariant balance >= 0 (when combined with a withdraw method).

Why it matters: invariants live in one place. Change the rule once, not in every caller.


2. Inheritance

A subclass reuses fields/methods of its parent and can specialize them.

public abstract class Vehicle {
    protected String licensePlate;
    public abstract double tollRate();
}

public class Car extends Vehicle {
    public double tollRate() { return 5.0; }
}

public class Truck extends Vehicle {
    private int axles;
    public double tollRate() { return 5.0 + axles * 2.0; }
}

Caution: prefer composition over inheritance. Inheritance creates strong coupling — a subclass depends on the parent's implementation, not just its interface.


3. Polymorphism

The same call site behaves differently depending on the runtime type.

List<Vehicle> queue = List.of(new Car(), new Truck());
double total = queue.stream().mapToDouble(Vehicle::tollRate).sum();

Two flavors:

FlavorResolved atExample
Compile-time (overloading)compileadd(int, int) vs add(String, String)
Runtime (overriding)runtimeVehicle::tollRate dispatched to Car or Truck

4. Abstraction

Expose a stable contract; hide the noise behind it.

public interface PaymentGateway {
    PaymentResult charge(Money amount, Card card);
}

// Caller doesn't care which provider — Stripe, Razorpay, mock.
class CheckoutService {
    private final PaymentGateway gateway;
    void checkout(Order o) { gateway.charge(o.total(), o.card()); }
}

The CheckoutService is testable (swap a mock gateway) and replaceable (switch providers without touching checkout logic).


Encapsulation vs Abstraction (commonly confused)

  • Encapsulation: "you don't get to read/write balance directly."
  • Abstraction: "you don't need to know how the payment happens, just call charge()."

Class Relationships (cheat sheet)

RelationshipUMLExample
Inheritance (is-a)hollow triangleCar is-a Vehicle
Composition (owns)filled diamondCar owns Engine (engine dies with car)
Aggregation (has)hollow diamondTeam has Player (players outlive team)
Association (uses)plain arrowOrder uses Customer
Dependency (uses temporarily)dashed arrowmethod param

Interview Tips

  • Don't recite the four pillars — demonstrate them in your design.
  • When asked "why this class?" answer in terms of single responsibility, not OOP buzzwords.
  • Be ready to defend composition over inheritance with a concrete example.

Finished reading?

Mark it done to track your progress in Designs.