OOP Fundamentals — The Four Pillars

Encapsulation, abstraction, inheritance, polymorphism. The vocabulary every LLD interview assumes you have internalised.

Concept Foundational
8 min read
oop encapsulation abstraction inheritance polymorphism

Summary#

Object-oriented design rests on four ideas that the LLD interview expects you to wield as second nature: encapsulation, abstraction, inheritance, and polymorphism. They are not four separate techniques you reach for sequentially; they are four lenses on the same act — drawing a clean boundary around something the rest of the system shouldn’t have to care about.

A useful one-line definition for each:

  • Encapsulation — bundle state with the behaviour that operates on it, and hide the state behind a controlled surface.
  • Abstraction — expose what something does; conceal how it does it.
  • Inheritance — name a relationship of specialization between types so common behaviour lives in one place.
  • Polymorphism — let calling code stay ignorant of which concrete type it is talking to, as long as the type honours an agreed interface.

If you can produce a small, honest example of each on demand, you have the foundation the rest of LLD interviewing is built on.

Why it matters#

Every LLD problem you will be handed — Parking Lot, Elevator, ATM, Hotel — reduces to which boundaries to draw and how to name them. The four pillars are the vocabulary for talking about those boundaries:

  • A reviewer who watches you stuff every field into public will down-grade you on encapsulation before you have written a single method.
  • A class hierarchy with the wrong root concept signals you do not have abstraction, only inheritance — and inheritance without abstraction is just code reuse with a worse coupling profile.
  • A switch (type) chain inside a method is a polite request from the code to introduce polymorphism; missing the cue is the most common LLD anti-pattern.
  • An IS-A relationship asserted carelessly produces a Liskov violation, which the interviewer will spot before you finish the diagram.

The pillars also map almost exactly onto the SOLID principles you will be expected to reference later — SRP is encapsulation said sharply, OCP is polymorphism said procedurally, LSP is inheritance said correctly, DIP is abstraction said about direction. Internalising the four lets the five fall into place.

How it works#

Encapsulation#

The mechanical move: data is private; access goes through methods. The conceptual move: the methods are the interface, and any change to the internal representation is invisible from the outside.

public final class BankAccount {
private long balanceMinor; // cents — internal representation
public BankAccount(long openingBalanceMinor) {
if (openingBalanceMinor < 0) {
throw new IllegalArgumentException("Opening balance must be non-negative");
}
this.balanceMinor = openingBalanceMinor;
}
public void deposit(long amountMinor) {
if (amountMinor <= 0) throw new IllegalArgumentException("Deposit must be positive");
this.balanceMinor += amountMinor;
}
public void withdraw(long amountMinor) {
if (amountMinor <= 0) throw new IllegalArgumentException("Withdrawal must be positive");
if (amountMinor > this.balanceMinor) throw new IllegalStateException("Insufficient funds");
this.balanceMinor -= amountMinor;
}
public long balanceMinor() {
return this.balanceMinor;
}
}

The class enforces invariants the field alone never could: non-negative balance, no overdraft, positive amounts. The choice to store long cents instead of double rupees is a representation decision the rest of the system never has to know.

Abstraction#

Encapsulation hides data; abstraction hides decisions. Abstraction is what you do when you name an interface so that callers can stay honest about what they actually depend on.

public interface PaymentMethod {
PaymentResult charge(Money amount, OrderId orderId);
void refund(OrderId orderId);
}

A Checkout that takes a PaymentMethod cannot accidentally couple to “we use Stripe”, because Stripe is not in the type signature. The concrete StripePayment, RazorpayPayment, or TestDoublePayment all satisfy the interface; the checkout sees only the verb.

The relationship to encapsulation:

Encapsulation. Hide how the state is stored behind methods. Single class. Concerned with the integrity of one object.

Abstraction. Hide which implementation is in play behind an interface. Multiple classes. Concerned with the shape of the seam between modules.

Inheritance#

Inheritance lets a subclass say “I am a specialised kind of my parent — I honour everything the parent promises, and I add or refine some of it.” It is the IS-A relationship made executable.

public abstract class Vehicle {
protected final String licensePlate;
protected Vehicle(String licensePlate) { this.licensePlate = licensePlate; }
public abstract double parkingFeePerHour();
public String licensePlate() { return licensePlate; }
}
public final class Car extends Vehicle {
public Car(String licensePlate) { super(licensePlate); }
@Override public double parkingFeePerHour() { return 50.0; }
}
public final class Motorcycle extends Vehicle {
public Motorcycle(String licensePlate) { super(licensePlate); }
@Override public double parkingFeePerHour() { return 20.0; }
}

This is inheritance used well: Vehicle names the shared concept and the contract; subclasses honour the contract with their own answers. A ParkingLot taking List<Vehicle> can charge any of them without knowing which.

Inheritance used badly is the more common case. Two heuristics will save you in interview:

  • IS-A, not HAS-A. A Car IS-A Vehicle. A Car HAS-A Engine. The second is composition, not inheritance — and composition is almost always the better default when the relationship is “made of,” not “is a kind of.”
  • No deep hierarchies. Past two levels, the model usually wants strategies or roles, not deeper inheritance. EmployeeManagerSeniorManagerVicePresident is a smell; Employee with a Role field is the answer.

Polymorphism#

With inheritance and abstraction in place, polymorphism is what they enable: a single call site that does the right thing for many concrete types.

public double totalRevenuePerHour(List<Vehicle> parked) {
double total = 0.0;
for (Vehicle v : parked) {
total += v.parkingFeePerHour(); // dispatches to Car, Motorcycle, …
}
return total;
}

The method neither knows nor cares which concrete vehicles are in the list. Add a Truck next quarter, and this code does not change. That is the property worth maximising — it is the procedural form of the Open-Closed Principle.

Two forms worth distinguishing:

  • Subtype (dynamic) polymorphism — the example above. Dispatch happens at runtime based on the actual object’s class.
  • Parametric polymorphism — generics. List<T> works for any T. Same goal (calling code stays type-agnostic), different mechanism (compile-time substitution).

The classic anti-pattern that polymorphism replaces:

// before — a switch-on-type that every new vehicle kind must edit
public double feeFor(Vehicle v) {
if (v instanceof Car) return 50.0;
if (v instanceof Motorcycle) return 20.0;
if (v instanceof Truck) return 100.0;
throw new IllegalArgumentException();
}

Any time you see this shape in an interview answer, the interviewer wants you to refactor it before they have to ask.

Variants and trade-offs#

PillarStrong formWeak formWhen weak is correct
EncapsulationPrivate fields, validating setters, immutable where possiblePublic fields, DTO-style classesPure data carriers crossing a serialization boundary (records, protobufs)
AbstractionInterface or abstract class as the public typeConcrete class everywhereThe interface would have exactly one implementation forever
InheritanceAbstract base + concrete leaves, depth ≤ 2Concrete extends concrete; deep chainsAlmost never — prefer composition
PolymorphismSubtype dispatch + small number of typesinstanceof chainsRarely defensible; bridge types or visitor for closed unions

When this is asked in interviews#

Three failure modes account for most points lost on this topic:

  • Confusing abstraction with encapsulation. A common cue: the interviewer asks “what’s the difference?” Be ready with the one-sentence form above — abstraction hides decisions, encapsulation hides state — and give one example each.
  • Asserting IS-A without checking LSP. If Square extends Rectangle comes out of your mouth, the interviewer will ask whether setWidth(5) on a Square referenced as a Rectangle does the right thing. The answer is no, and the example is in the canon for that reason.
  • Building a class hierarchy when the variation belongs in a field. “A Vehicle subclass per fuel type” is almost always wrong; Vehicle with a FuelType field plus a strategy for refuelling is almost always right. Watch for orthogonal axes of variation and prefer composition for each.

The strongest signal you can send: when a follow-up adds a new vehicle / payment method / animal, you change no existing code, only add a new class. That is the four pillars working in concert.

The four pillars are the entry point into a wider set of ideas you will use across every LLD problem:

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.