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 min
The four pillars are: Encapsulation (bundling data + methods, hiding internals), Abstraction (showing only essential features), Inheritance (acquiring properties from parent), Polymorphism (one interface, many forms).
Encapsulation

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).

C++ — All Four Pillars
#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
Compile-time (static) = resolved during compilation — function overloading, operator overloading. Runtime (dynamic) = resolved during execution — virtual functions + overriding via vtable.
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
C++ — Compile-Time (Overloading)
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
C++ — Runtime (Overriding + Virtual)
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 virtual keyword, 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
Overloading: Same name, different parameters, SAME class. Overriding: Same name, same parameters, DIFFERENT classes (parent-child). Overloading = compile time. Overriding = runtime.
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
Abstract class: partial implementation, IS-A relationship, can have data members. Pure abstract class (all pure virtual) = C++ equivalent of interface. Multiple inheritance of pure abstract classes achieves "interfaces" in C++.
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
C++ — Abstract Class vs Interface
// ─── 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
Association: objects are related (Teacher-Student). Aggregation: "HAS-A" with independent lifetime (Department-Employee). Composition: "HAS-A" with dependent lifetime (House-Room, child dies with parent).
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 ◆
C++ — Aggregation vs Composition
// ─── 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 min
SOLID = 5 principles for maintainable OOP: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion. Asked in almost every senior interview.
S
Single Responsibility

A class should have only ONE reason to change. Bad: UserService handles auth + email + DB. Good: separate classes.

O
Open-Closed

Open for extension, closed for modification. Add new features via new classes/inheritance, not by editing existing code.

L
Liskov Substitution

Subclasses should be substitutable for parent. If code works with Animal, it must work with Dog without surprises.

I
Interface Segregation

Many specific interfaces > one fat interface. Don't force classes to implement methods they don't use.

D
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 min
Calling one constructor from another. In C++: use member initializer lists and delegating constructors (C++11). Child calls parent constructor in initializer list. Reduces code duplication.
C++ — Constructor Chaining
class 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 min
Shallow: copies pointer values — both objects point to same heap memory. Deep: allocates new memory and copies actual data — fully independent clones. Shallow = fast, dangerous. Deep = safe, slower.
C++ — Shallow vs Deep Copy
class 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
Static members belong to the CLASS — shared by all objects, no need to create instance. Instance members belong to OBJECTS — each has its own copy. Static = one per class. Instance = one per object.
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
C++ — Static vs Instance
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
Control visibility: private (same class only), protected (same class + derived classes), public (everywhere). In C++, class members are private by default, struct members are public by default.
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. private inheritance makes everything private in child.
  • Default access: class = private, struct = public

Diamond Problem

ConceptClassic3 min
D inherits from B and C, both inherit from A. D gets TWO copies of A — ambiguity. C++ solves with virtual inheritance — ensures single copy of base class.
Diamond Problem Inheritance Diagram Class D inherits from B and C, and both B and C inherit from A, creating ambiguity about which A is inherited. A B C D Which A?
C++ — Virtual Inheritance Fix
class 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 min
Providing objects a class needs (dependencies) from outside, rather than creating them inside. Makes code testable, flexible, loosely coupled. The "D" in SOLID.
C++ — Without DI (Bad)
class OrderService {
    MySQLDatabase db;   // hardcoded dependency!
public:
    OrderService() : db() {}
    // Can't test without real MySQL.
    // Can't switch to PostgreSQL.
};
C++ — With DI (Good)
// 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
Cohesion = how related are responsibilities within a module (HIGH = good). Coupling = how dependent modules are on each other (LOW = good). Goal: High cohesion, low coupling.
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 min
State cannot change after creation. Any "modification" creates a new object. Thread-safe by default, safe for hash keys, easier to reason about. Example: std::string in many cases.
C++ — Immutable Class
class 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 min
Reusable solutions to common problems. Singleton: ensure one instance. Factory: create objects without exposing logic. Strategy: swap algorithms at runtime.

1. Singleton Pattern

Only ONE instance ever. Use for: config manager, logger, connection pool.

C++ — Thread-Safe Singleton
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.

C++ — Factory Pattern
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.

C++ — Strategy Pattern
// 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?
Global state makes testing hard. Hidden dependencies. Violates Single Responsibility. Thread-safety complexity. Many consider it an anti-pattern. Prefer dependency injection.
Factory vs Abstract Factory?
Factory: Creates one product type. Abstract Factory: Creates families of related products (e.g., WindowsButton + WindowsCheckbox vs MacButton + MacCheckbox).
Strategy vs State pattern?
Both swap behavior via composition. Strategy: Client chooses algorithm. State: Object changes behavior based on internal state. State transitions happen internally; strategies are swapped externally.

Ready to test OOPs?

Turn the concepts into interview-speed recall with the focused quiz.

Start practice
Home 01. DBMS 02. OOPs 03. Computer Networks 04. Operating System 05. C++ Core 05. Java Core 05. Python Core 06. Puzzles 07. Linux 08. Git 09. SQL 10. Projects 11. System Design 12. DSA Concepts