Why these four words matter
Every LLD interview, every code review, every “why is this class like this?” conversation runs on the same four-word vocabulary. Get fluent in them and you can name what good design is doing — which is the first step to producing it yourself.
1. Encapsulation — guard your invariants
Encapsulation is bundling data + the methods that operate on it, and hiding the internals so the outside world can only touch them through a controlled surface.
The point isn’t secrecy for its own sake — it’s protecting invariants (rules that must always be true). If a BankAccount exposes account.balance = -500, the invariant “balance ≥ 0” is unenforceable. Make balance private and force all changes through deposit()/withdraw(), and the rule lives in exactly one place.
Rule of thumb: if breaking a field would break a business rule, that field should not be publicly settable.
2. Abstraction — expose the what, hide the how
Abstraction is modeling something by its essential behavior while hiding implementation detail. A List tells you add, get, size — you don’t care if it’s an array or linked nodes underneath.
Encapsulation and abstraction are cousins: encapsulation is the mechanism (private fields, methods), abstraction is the result (a clean conceptual surface).
3. Inheritance — reuse via “is-a” (carefully)
Inheritance lets a subclass acquire fields/behavior from a superclass. SavingsAccount extends BankAccount because a savings account is a bank account.
It’s powerful but the most over-used pillar. Dangers:
- Tight coupling — the subclass depends on the parent’s internal implementation.
- Fragile base class — a change in the parent silently breaks children.
- False “is-a” — people reach for inheritance to reuse code, not to model a real hierarchy.
The #1 LLD interview red flag is reaching for extends just to reuse code. If the relationship isn’t a true is-a, you want composition. You’ll meet the antidote in the next lesson.
4. Polymorphism — one interface, many behaviors
Polymorphism lets a single interface represent many concrete types, with the right behavior chosen at runtime.
interface PaymentMethod { pay(amount: number): Receipt; }
class CardPayment implements PaymentMethod { pay(a) { /* ... */ } }
class UpiPayment implements PaymentMethod { pay(a) { /* ... */ } }
function checkout(method: PaymentMethod, amount: number) {
return method.pay(amount); // doesn't know or care which one
}checkout is written once and works for every present and future payment method. This is the seed of nearly every design pattern you’ll learn — program to an interface, not an implementation.
Program to an interface, not an implementation. Almost every design pattern in Level 3 is just a disciplined application of this one sentence. Tattoo it on your brain.
How they work together
A good design uses all four at once: abstraction decides the interface, encapsulation protects the data behind it, polymorphism lets many types fulfill it, and inheritance (sparingly) shares the truly common parts.
Take the assessment below — the design problem is where this stops being trivia and starts being a skill.