OOP Basics and 4 Pillars — Interview Revision
Priority: MEDIUM — Likely asked as a warm-up before diving into code Spoken answer target: under 45 seconds each
1. What is OOP?
Definition:
OOP is a programming paradigm that organizes code around objects — bundles of data and behavior — rather than functions and logic. It models real-world entities as self-contained units.
Key Point / Mechanism: In procedural code, data and functions are separate — anyone can corrupt the data (e.g., set balance = -99999). OOP bundles them inside a class: data is private, behavior is exposed through controlled methods. This makes systems more maintainable, scalable, and easier to reason about.
Real-world use case: A BankAccount class hides its balance (data) and only allows deposit/withdraw (controlled methods). This is impossible in C-style code without discipline.
Likely follow-up:
"Why OOP over procedural?" OOP naturally maps to real-world problems, enforces data protection, enables code reuse via inheritance, and supports writing code that works on types not yet invented (polymorphism).
Common mistake: Thinking OOP is just about using classes. Real OOP means proper encapsulation — not exposing every field with a getter/setter.
2. Encapsulation
Definition:
Encapsulation is bundling data and the methods that operate on that data into a single unit (class), and restricting direct access to the internal state using access modifiers.
Real-world analogy (for the interview):
Think of an ATM. The internal state — cash reserves, account data, transaction logs — is completely hidden. You interact ONLY through the interface: insert card, enter PIN, select amount. You cannot reach in and grab cash directly. The ATM validates everything internally.
Key Point:
- Data is
private, methods arepublic - All changes go through methods that can validate, log, or trigger side effects
- You can change internal implementation without breaking the caller
class BankAccount {
private:
double balance;
public:
bool withdraw(double amount) {
if (amount > balance) return false; // Validates!
balance -= amount;
return true;
}
double getBalance() const { return balance; }
};
// acc.balance = -99999; // Compile error — private!Likely follow-up:
"Aren't getters/setters breaking encapsulation?" Yes, if you blindly add them for every field — that's just a struct with extra steps. Real encapsulation is adding validation, side effects, or computed properties. A class that only has getters/setters has no real encapsulation.
Common mistake:
Adding getX() and setX() for every private field. If you expose everything, you have not encapsulated anything — you have just moved the problem.
3. Abstraction
Definition:
Abstraction means exposing only the essential details and hiding the underlying complexity — showing WHAT something does, not HOW it does it.
Real-world analogy (for the interview):
Driving a car. The interface you see is: steering wheel, pedals, gear shift. The complexity hidden is: engine combustion, fuel injection, electronic control units, transmission mechanics. You don't need to understand internal combustion to drive. The car abstracts away the complexity.
Key Point:
- Implemented via abstract classes and interfaces
- Users call
sort(arr)— they don't know it uses introsort with 3-way partitioning internally - Lets you change implementation without breaking callers
class PaymentProcessor {
public:
virtual bool processPayment(double amount) = 0; // Abstract interface
virtual ~PaymentProcessor() = default;
};
// Caller doesn't know or care if it's Stripe, PayPal, or Razorpay
void checkout(PaymentProcessor& processor, double total) {
processor.processPayment(total); // Works with ANY implementation
}Likely follow-up:
"Difference between Encapsulation and Abstraction?" Encapsulation is HOW you hide (access modifiers, private data). Abstraction is WHAT you hide (complexity, unnecessary details). TV analogy: Encapsulation = the circuit board is sealed inside. Abstraction = the remote has only power/volume/channel, you never see signal decoding.
Common mistake: Treating them as the same concept. Abstraction is about simplifying the interface. Encapsulation is about protecting the internal state.
4. Inheritance
Definition:
Inheritance is a mechanism where a derived class acquires the properties and behavior of a base class, enabling code reuse and establishing an IS-A relationship.
Real-world analogy (for the interview):
A vehicle hierarchy. A Car IS-A Vehicle — it inherits color, speed, fuel capacity, and the ability to start/stop/refuel. But a Car also adds its own properties: number of doors, trunk size, ability to open the trunk. Same foundation, specialized behavior.
Key Point:
- Use
publicinheritance for true IS-A relationships - Constructor/destructor order: Base first, Derived last (construction); Derived first, Base last (destruction)
- Java supports single inheritance only. C++ allows multiple inheritance.
class Vehicle {
public:
void start() { cout << "Engine started"; }
virtual ~Vehicle() = default;
};
class Car : public Vehicle { // Car IS-A Vehicle
int doors;
public:
Car(int d) : doors(d) {}
void openTrunk() { cout << "Trunk opened"; }
};
Car c(4);
c.start(); // Inherited
c.openTrunk(); // Own methodLikely follow-up:
"Inheritance vs Composition — when to use which?" Inheritance = IS-A (Dog IS-A Animal). Composition = HAS-A (Car HAS-A Engine). Prefer composition — it is more flexible, loosely coupled, and avoids fragile base class problems. Only use inheritance for true IS-A relationships that need polymorphism.
Common mistake:
Using inheritance for code reuse alone. If Stack inherits from vector just to get push_back, it inherits insert, erase, and everything else — that breaks the Stack contract. Use composition.
5. Polymorphism (HIGHEST PRIORITY)
Definition:
Polymorphism means "many forms" — the same interface behaves differently depending on the underlying type. One function call, multiple behaviors.
Real-world analogy (for the interview):
A remote control's Play button. Press Play on a DVD player — it plays a movie. Press Play on a music player — it plays a song. Press Play on a game console — it resumes the game. Same button (interface), different behavior depending on the device (type). The remote does not need to know which device it is controlling.
Two types:
- Compile-time (static): Function overloading, operator overloading — resolved at compile time
- Runtime (dynamic): Virtual functions — resolved at runtime via vtable
C++ example — both types:
// COMPILE-TIME: function overloading
class Printer {
public:
void print(int val) { cout << "Int: " << val; }
void print(double val) { cout << "Double: " << val; }
void print(const string& s) { cout << "String: " << s; }
};
// RUNTIME: virtual functions
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double r;
public:
Circle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
};
class Rectangle : public Shape {
double w, h;
public:
Rectangle(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
};
void printArea(const Shape& s) { // Works for ANY shape
cout << s.area();
}
// printArea(Circle(5)); // 78.5
// printArea(Rectangle(4,6)); // 24Java example (runtime polymorphism):
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
double area() { return Math.PI * r * r; }
}
class Rectangle extends Shape {
double w, h;
Rectangle(double w, double h) { this.w = w; this.h = h; }
double area() { return w * h; }
}
// Works with any Shape subclass
void printArea(Shape s) {
System.out.println(s.area());
}Likely follow-up:
"How does runtime polymorphism work internally?" Through vtable and vptr. Each class with virtual functions gets a vtable (array of function pointers). Each object gets a vptr pointing to its class's vtable. When you call a virtual function through a base pointer, the CPU looks up the correct function in the vtable at runtime.
Common mistake:
Forgetting the virtual keyword. Without it, calling through a base pointer always calls the BASE class version — not the override. This is method hiding, not overriding.
// BUG — missing virtual
class Base {
void speak() { cout << "Base"; } // NOT virtual
};
class Derived : public Base {
void speak() { cout << "Derived"; }
};
Base* p = new Derived();
p->speak(); // "Base" — WRONG! Dynamic dispatch never triggered.Pillar Comparison Table
| Pillar | Core Idea | Mechanism | Real-world |
|---|---|---|---|
| Encapsulation | Hide internal state | private + public methods | ATM machine |
| Abstraction | Hide complexity | Abstract classes / interfaces | Car driving interface |
| Inheritance | Reuse + extend | class Derived : public Base | Vehicle hierarchy |
| Polymorphism | Same interface, many behaviors | virtual functions / overloading | Remote control Play button |