Object-Oriented Programming
15 Core OOP Concepts — The foundation of software design. Master these, and you'll think like an architect, not just a coder.
Four Pillars of OOP
CoreMost Asked5 minEncapsulation
Bundling data (variables) and methods into a single unit (class). Hiding internal state using private/protected. Access through public getters/setters.
Abstraction
Hiding complex implementation, showing only essential features. Like driving a car — you use the steering wheel, not engine internals. Achieved via abstract classes.
Inheritance
Child class acquires properties and behaviors of parent class. Promotes code reuse. "IS-A" relationship: Dog IS-A Animal. Types: single, multi-level, hierarchical.
Polymorphism
One name, many forms. Same function behaves differently based on object type. Types: compile-time (overloading) and runtime (overriding via virtual functions).
#include <iostream> using namespace std; // ─── ENCAPSULATION: private data + public methods ─── class BankAccount { private: double balance; // hidden from outside public: BankAccount() : balance(0) {} void deposit(double amount) { if (amount > 0) balance += amount; } double getBalance() { return balance; } }; // ─── ABSTRACTION: hide complexity ─── class Shape { public: virtual double area() = 0; // pure virtual = what, not how virtual ~Shape() {} }; // ─── INHERITANCE + POLYMORPHISM ─── class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} // Overriding — same method, different behavior double area() override { return 3.14159 * radius * radius; } }; class Rectangle : public Shape { double w, h; public: Rectangle(double w, double h) : w(w), h(h) {} double area() override { return w * h; } };
Key Interview Points
- Encapsulation provides data hiding and security. Can validate data before setting.
- Abstraction reduces complexity for the user. Shows WHAT, hides HOW.
- Inheritance enables code reuse but can lead to tight coupling. Prefer composition over inheritance.
- Polymorphism enables loose coupling. New shapes can be added without changing existing code.
Common Confusion
Encapsulation vs Abstraction: Encapsulation is about BUNDLING and HIDING data. Abstraction is about showing only RELEVANT details. Encapsulation = implementation technique, Abstraction = design concept.
Runtime vs Compile-Time Polymorphism
CoreMust Know4 min| Aspect | Compile-Time (Static) | Runtime (Dynamic) |
|---|---|---|
| When Resolved | During compilation | During execution |
| Achieved By | Function overloading, templates | Virtual functions + overriding |
| Binding | Early binding | Late binding (vtable) |
| Performance | Faster (no lookup) | Slightly slower (vtable indirection) |
| Keyword | None needed | virtual, override |
class Calculator { public: // Same name, different parameters → resolved at compile time int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int add(int a, int b, int c) { return a + b + c; } }; // Compiler picks which add() to call based on argument types
class Animal { public: virtual void speak() { cout << "Some sound" << endl; } virtual ~Animal() {} }; class Dog : public Animal { public: void speak() override { cout << "Bark!" << endl; } }; class Cat : public Animal { public: void speak() override { cout << "Meow!" << endl; } }; // Decided at RUNTIME based on actual object type Animal* animal = new Dog(); animal->speak(); // Output: "Bark!" (not "Some sound") delete animal;
Key Interview Points
- Runtime polymorphism needs: inheritance + virtual function + base class pointer/reference
-
Without
virtualkeyword, base class function is called — no polymorphism - Runtime polymorphism uses vtable internally — array of function pointers per class
- Each object gets a hidden vptr that points to its class's vtable
Competitive Edge
Mention
CRTP (Curiously Recurring Template Pattern) —
achieves polymorphism at compile-time without virtual overhead.
Used in high-performance systems where vtable cost matters.
class Derived : public Base<Derived> {}
Method Overloading vs Overriding
Core3 min| Aspect | Overloading | Overriding |
|---|---|---|
| Scope | Within same class | Parent and child classes |
| Parameters | Must be different | Must be exactly same |
| Return Type | Can differ | Must be same (or covariant) |
| Binding | Compile-time | Runtime |
| virtual needed | No | Yes (in C++) |
| Inheritance needed | No | Yes |
Common Mistakes
✗ "Changing only return type is overloading" — causes
compilation error.
✗ "Overriding works in same class" —
needs inheritance.
✗ "Static methods can be overridden" —
static methods are hidden, not overridden. No polymorphism.
Interface vs Abstract Class
CoreVery Common4 min| Aspect | Abstract Class | Interface (Pure Abstract) |
|---|---|---|
| Methods | Mix of virtual + concrete | ALL pure virtual (= 0) |
| Data Members | Can have data | Typically no data |
| Multiple Inheritance | Diamond problem risk | Safe (no data to duplicate) |
| Constructor | Can have | Typically no |
| When to Use | Related classes, shared code | Unrelated classes, shared behavior |
// ─── ABSTRACT CLASS (has some implementation) ─── class Animal { protected: string name; public: Animal(string n) : name(n) {} void sleep() { cout << name << " is sleeping" << endl; } virtual void makeSound() = 0; // pure virtual virtual ~Animal() {} }; // ─── INTERFACE (all pure virtual, no data) ─── class Flyable { public: virtual void fly() = 0; virtual ~Flyable() {} }; class Swimmable { public: virtual void swim() = 0; virtual ~Swimmable() {} }; // Duck: IS-A Animal, CAN fly, CAN swim class Duck : public Animal, public Flyable, public Swimmable { public: Duck() : Animal("Duck") {} void makeSound() override { cout << "Quack!" << endl; } void fly() override { cout << "Flying..." << endl; } void swim() override { cout << "Swimming..." << endl; } };
When to Use Which
- Abstract class: Related classes with shared code (Animal → Dog, Cat)
- Interface (pure abstract): Unrelated classes sharing behavior (Car, Bird → Movable)
- Interface: When you need multiple inheritance of type
Association, Aggregation, Composition
Concept4 min| Aspect | Association | Aggregation | Composition |
|---|---|---|---|
| Relationship | Uses / Knows | HAS-A (weak) | HAS-A (strong) |
| Ownership | No ownership | Partial | Full ownership |
| Child Lifetime | Independent | Independent | Dependent |
| Example | Teacher ↔ Student | Dept → Employee | House → Room |
| UML | Plain line | Empty diamond ◇ | Filled diamond ◆ |
// ─── AGGREGATION: Employee exists independently ─── class Employee { /* ... */ }; class Department { vector<Employee*> employees; // pointers → don't own public: void addEmployee(Employee* e) { employees.push_back(e); // passed from outside } // ~Department does NOT delete employees }; // ─── COMPOSITION: Room dies with House ─── class Room { public: string name; Room(string n) : name(n) {} }; class House { vector<Room> rooms; // owns by value public: House() { rooms.push_back(Room("Bedroom")); // created inside rooms.push_back(Room("Kitchen")); } // ~House destroys all Rooms automatically };
Competitive Edge
The key distinction: who creates and destroys the child? Aggregation = child passed in (pointer), parent doesn't delete. Composition = child created inside (by value or unique_ptr), automatically destroyed with parent.
SOLID Principles
ConceptDesign Principles5 minSingle Responsibility
A class should have only ONE reason to change. Bad: UserService handles auth + email + DB. Good: separate classes.
Open-Closed
Open for extension, closed for modification. Add new features via new classes/inheritance, not by editing existing code.
Liskov Substitution
Subclasses should be substitutable for parent. If code works with Animal, it must work with Dog without surprises.
Interface Segregation
Many specific interfaces > one fat interface. Don't force classes to implement methods they don't use.
Dependency Inversion
Depend on abstractions, not concretions. OrderService depends on Database interface, not MySQLDatabase directly.
Memory Aid
- S: One class = One job
- O: Extend, don't modify
- L: Children behave like parents
- I: Thin interfaces > fat interfaces
- D: Depend on abstractions
Classic LSP Violation
Square extends Rectangle. But setWidth() in Square must also set
height. This breaks code expecting Rectangle:
r.setWidth(5); r.setHeight(10); assert(r.area()==50);
fails. They're different shapes — don't use inheritance here.
Constructor Chaining
Concept3 minclass Employee { string name; int age; string department; public: // Full constructor Employee(string n, int a, string d) : name(n), age(a), department(d) {} // Delegating constructor → calls full constructor Employee(string n, int a) : Employee(n, a, "General") {} // C++11 // Another delegation Employee(string n) : Employee(n, 0, "General") {} }; // Child → Parent via initializer list class Manager : public Employee { int teamSize; public: Manager(string n, int a, int ts) : Employee(n, a, "Management"), // call parent teamSize(ts) {} };
Key Interview Points
- C++11 introduced delegating constructors — one constructor calls another of same class
- Parent constructor called in initializer list, not inside body
- If parent has no default constructor, child MUST explicitly call it
- Member variables initialized in declaration order, not initializer list order
Deep vs Shallow Copy
ConceptTricky4 minclass Person { char* name; int age; public: Person(const char* n, int a) : age(a) { name = new char[strlen(n) + 1]; strcpy(name, n); } // ─── SHALLOW COPY (default) — DANGEROUS ─── // Default copy constructor just copies pointer value // Both objects point to SAME memory → double free crash! // ─── DEEP COPY — SAFE ─── Person(const Person& other) : age(other.age) { name = new char[strlen(other.name) + 1]; strcpy(name, other.name); // allocate + copy } ~Person() { delete[] name; } }; Person a("John", 25); Person b = a; // calls deep copy constructor // a and b have independent memory — safe!
Key Interview Points
- Default copy constructor does shallow copy (memberwise copy)
- If class has raw pointers → MUST write deep copy constructor
- Rule of Three: If you write destructor, copy constructor, or copy assignment — write ALL three
- Rule of Five (C++11): Add move constructor + move assignment
- Use smart pointers (unique_ptr) to avoid manual memory management entirely
Static vs Instance Members
Core3 min| Aspect | Static | Instance |
|---|---|---|
| Belongs to | Class | Object |
| Memory | One copy shared | Separate per object |
| Access | ClassName::member |
obj.member |
| Can access instance? | No | Yes (both) |
| this pointer | No | Yes |
| Virtual possible? | No | Yes |
class Employee { string name; // instance — per object static int totalCount; // static — shared by all public: Employee(string n) : name(n) { totalCount++; // increment shared counter } static int getTotal() { // can't use 'name' here — no 'this' pointer return totalCount; } }; int Employee::totalCount = 0; // define outside class Employee e1("John"); Employee e2("Jane"); cout << Employee::getTotal(); // 2
Common Mistake
"Static methods can be overridden" — Wrong! Static methods are resolved by reference type at compile time. No vtable involved, no polymorphism. They are hidden, not overridden.
Access Modifiers
Core2 min| Modifier | Same Class | Derived Class | Outside |
|---|---|---|---|
private |
✓ | ✗ | ✗ |
protected |
✓ | ✓ | ✗ |
public |
✓ | ✓ | ✓ |
Key Interview Points
- C++ has friend keyword — grants private access to specific classes/functions
-
Inheritance access:
class Dog : public Animal— public keeps access levels.privateinheritance makes everything private in child. -
Default access:
class= private,struct= public
Diamond Problem
ConceptClassic3 minclass A { public: void show() { cout << "A"; } }; // virtual inheritance → only ONE copy of A in D class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { }; D d; d.show(); // No ambiguity! Only one A::show()
How Other Languages Handle It
- Java: No multiple class inheritance. Uses interfaces instead.
- C++: Virtual inheritance — single copy of shared base
- Python: MRO (Method Resolution Order) with C3 linearization
Dependency Injection
ConceptPractical4 minclass OrderService { MySQLDatabase db; // hardcoded dependency! public: OrderService() : db() {} // Can't test without real MySQL. // Can't switch to PostgreSQL. };
// Abstraction (interface) class IDatabase { public: virtual void save(const Order& order) = 0; virtual ~IDatabase() {} }; class OrderService { IDatabase& db; // depends on abstraction public: // CONSTRUCTOR INJECTION OrderService(IDatabase& database) : db(database) {} void placeOrder(const Order& order) { db.save(order); } }; // Usage — inject any implementation MySQLDatabase mysql; OrderService service(mysql); // For testing — inject mock MockDatabase mock; OrderService testService(mock);
Types of DI
- Constructor Injection: Dependencies via constructor. Most common, makes deps explicit.
- Setter Injection: Via setter methods. For optional dependencies.
- Interface Injection: Class implements injector interface. Rare.
Cohesion vs Coupling
Concept3 min| Aspect | Cohesion | Coupling |
|---|---|---|
| Definition | Relatedness within a module | Dependence between modules |
| Goal | HIGH is good | LOW is good |
| Bad Example | One class: auth + email + payments | OrderService creates MySQLDatabase |
| Good Example | AuthService only handles auth | OrderService depends on IDatabase |
Competitive Edge
Law of Demeter: Only talk to your immediate
friends. Avoid a.getB().getC().doSomething() — this
creates high coupling. Each dot is a dependency you're leaking.
Immutable Objects
ConceptPractical3 minclass ImmutablePoint { const int x; const int y; public: ImmutablePoint(int x, int y) : x(x), y(y) {} // Only getters, no setters int getX() const { return x; } int getY() const { return y; } // "Modification" returns new object ImmutablePoint move(int dx, int dy) const { return ImmutablePoint(x + dx, y + dy); } }; ImmutablePoint p1(3, 4); ImmutablePoint p2 = p1.move(1, 2); // p1 unchanged, p2 is (4,6)
How to Make a Class Immutable in C++
- Make all member variables
const -
No setter methods — only getters marked
const - Initialize everything in constructor initializer list
- Return new objects instead of modifying state
- For mutable members (like pointers), return copies not references
Design Patterns: Singleton, Factory, Strategy
ConceptVery Common6 min1. Singleton Pattern
Only ONE instance ever. Use for: config manager, logger, connection pool.
class Logger { private: Logger() {} // private constructor Logger(const Logger&) = delete; // no copy Logger& operator=(const Logger&) = delete; public: static Logger& getInstance() { static Logger instance; // thread-safe in C++11 return instance; } void log(const string& msg) { cout << "[LOG] " << msg << endl; } }; // Usage Logger::getInstance().log("Server started");
2. Factory Pattern
Create objects without exposing instantiation logic. Client uses
factory, not new.
class Shape { public: virtual void draw() = 0; virtual ~Shape() {} }; class Circle : public Shape { public: void draw() override { cout << "Drawing Circle" << endl; } }; class Rectangle : public Shape { public: void draw() override { cout << "Drawing Rect" << endl; } }; // Factory class ShapeFactory { public: static unique_ptr<Shape> create(const string& type) { if (type == "circle") return make_unique<Circle>(); if (type == "rectangle") return make_unique<Rectangle>(); return nullptr; } }; // Client doesn't know about Circle/Rectangle auto shape = ShapeFactory::create("circle"); shape->draw();
3. Strategy Pattern
Define a family of algorithms, make them interchangeable at runtime.
// Strategy interface class PaymentStrategy { public: virtual void pay(int amount) = 0; virtual ~PaymentStrategy() {} }; class CreditCard : public PaymentStrategy { public: void pay(int amount) override { cout << "Paid $" << amount << " via Credit Card" << endl; } }; class PayPal : public PaymentStrategy { public: void pay(int amount) override { cout << "Paid $" << amount << " via PayPal" << endl; } }; // Context class ShoppingCart { unique_ptr<PaymentStrategy> strategy; public: void setStrategy(unique_ptr<PaymentStrategy> s) { strategy = move(s); } void checkout(int amount) { strategy->pay(amount); // delegates to strategy } }; // Switch strategies at runtime ShoppingCart cart; cart.setStrategy(make_unique<CreditCard>()); cart.checkout(100); cart.setStrategy(make_unique<PayPal>()); cart.checkout(50);
When to Use Each
- Singleton: One instance needed (config, logging, thread pool)
- Factory: Object creation complex or should be decoupled
- Strategy: Multiple algorithms, want to swap at runtime
Common Follow-ups
What are the problems with Singleton?
Factory vs Abstract Factory?
Strategy vs State pattern?
Ready to test OOPs?
Turn the concepts into interview-speed recall with the focused quiz.