Module 08: Real-World OOP & Interview Prep
Goal: Tie everything together with a real project, UML basics, and comprehensive interview Q&A. Time: 2 days of focused study Prerequisites: All previous modules
Table of Contents
- Real-World Project: Library Management System
- UML Class Diagrams
- OOP Design Exercise: Design a Parking Lot
- 100+ Interview Questions — Quick Fire
- Coding Challenges
- Final Checklist
1. Library Management System
A complete project demonstrating ALL OOP concepts together.
#include <iostream>
#include <vector>
#include <memory>
#include <map>
#include <chrono>
#include <algorithm>
using namespace std;
// ========== INTERFACES (ISP) ==========
class ISearchable {
public:
virtual vector<string> search(const string& query) const = 0;
virtual ~ISearchable() = default;
};
class IBorrowable {
public:
virtual bool borrow(int userId) = 0;
virtual bool returnItem() = 0;
virtual bool isAvailable() const = 0;
virtual ~IBorrowable() = default;
};
class INotifiable {
public:
virtual void notify(const string& message) = 0;
virtual ~INotifiable() = default;
};
// ========== ENTITIES (SRP) ==========
// Abstract base class for all library items
class LibraryItem : public IBorrowable {
protected:
string id;
string title;
bool available;
int borrowedBy;
public:
LibraryItem(string id, string title)
: id(id), title(title), available(true), borrowedBy(-1) {}
bool borrow(int userId) override {
if (!available) return false;
available = false;
borrowedBy = userId;
return true;
}
bool returnItem() override {
available = true;
borrowedBy = -1;
return true;
}
bool isAvailable() const override { return available; }
string getId() const { return id; }
string getTitle() const { return title; }
virtual string getType() const = 0;
virtual string getDetails() const = 0;
virtual ~LibraryItem() = default;
};
// Concrete items (Inheritance + Polymorphism)
class Book : public LibraryItem {
string author;
string isbn;
int pages;
public:
Book(string id, string title, string author, string isbn, int pages)
: LibraryItem(id, title), author(author), isbn(isbn), pages(pages) {}
string getType() const override { return "Book"; }
string getDetails() const override {
return title + " by " + author + " (ISBN: " + isbn + ", " + to_string(pages) + "pp)";
}
string getAuthor() const { return author; }
};
class DVD : public LibraryItem {
string director;
int durationMinutes;
public:
DVD(string id, string title, string director, int dur)
: LibraryItem(id, title), director(director), durationMinutes(dur) {}
string getType() const override { return "DVD"; }
string getDetails() const override {
return title + " directed by " + director + " (" + to_string(durationMinutes) + " min)";
}
};
class Magazine : public LibraryItem {
int issueNumber;
string publisher;
public:
Magazine(string id, string title, string publisher, int issue)
: LibraryItem(id, title), publisher(publisher), issueNumber(issue) {}
string getType() const override { return "Magazine"; }
string getDetails() const override {
return title + " #" + to_string(issueNumber) + " by " + publisher;
}
};
// User types (Inheritance)
class User : public INotifiable {
protected:
int id;
string name;
string email;
vector<string> borrowedItems;
int maxBorrows;
public:
User(int id, string name, string email, int maxB)
: id(id), name(name), email(email), maxBorrows(maxB) {}
void notify(const string& message) override {
cout << "[NOTIFY " << name << "] " << message << endl;
}
bool canBorrow() const { return (int)borrowedItems.size() < maxBorrows; }
void addBorrowed(const string& itemId) { borrowedItems.push_back(itemId); }
void removeBorrowed(const string& itemId) {
borrowedItems.erase(
remove(borrowedItems.begin(), borrowedItems.end(), itemId),
borrowedItems.end());
}
int getId() const { return id; }
string getName() const { return name; }
virtual string getRole() const = 0;
virtual ~User() = default;
};
class Student : public User {
public:
Student(int id, string name, string email) : User(id, name, email, 3) {}
string getRole() const override { return "Student"; }
};
class Faculty : public User {
public:
Faculty(int id, string name, string email) : User(id, name, email, 10) {}
string getRole() const override { return "Faculty"; }
};
// ========== SERVICES (SRP + DIP) ==========
// Catalog service — manages items
class CatalogService : public ISearchable {
map<string, unique_ptr<LibraryItem>> items;
public:
void addItem(unique_ptr<LibraryItem> item) {
string id = item->getId();
items[id] = move(item);
}
LibraryItem* getItem(const string& id) {
auto it = items.find(id);
return it != items.end() ? it->second.get() : nullptr;
}
vector<string> search(const string& query) const override {
vector<string> results;
for (const auto& [id, item] : items) {
if (item->getTitle().find(query) != string::npos ||
item->getDetails().find(query) != string::npos) {
results.push_back(id);
}
}
return results;
}
void listAll() const {
for (const auto& [id, item] : items) {
cout << "[" << item->getType() << "] " << item->getDetails()
<< (item->isAvailable() ? " ✓" : " ✗") << endl;
}
}
};
// Borrowing service — handles borrow/return logic
class BorrowService {
CatalogService& catalog;
map<int, User*> users;
public:
BorrowService(CatalogService& cat) : catalog(cat) {}
void registerUser(User* user) {
users[user->getId()] = user;
}
bool borrowItem(int userId, const string& itemId) {
auto userIt = users.find(userId);
if (userIt == users.end()) {
cout << "User not found!" << endl;
return false;
}
User* user = userIt->second;
LibraryItem* item = catalog.getItem(itemId);
if (!item) { cout << "Item not found!" << endl; return false; }
if (!item->isAvailable()) { cout << "Item not available!" << endl; return false; }
if (!user->canBorrow()) { cout << "Borrow limit reached!" << endl; return false; }
item->borrow(userId);
user->addBorrowed(itemId);
user->notify("You borrowed: " + item->getTitle());
return true;
}
bool returnItem(int userId, const string& itemId) {
auto userIt = users.find(userId);
if (userIt == users.end()) return false;
User* user = userIt->second;
LibraryItem* item = catalog.getItem(itemId);
if (!item) return false;
item->returnItem();
user->removeBorrowed(itemId);
user->notify("You returned: " + item->getTitle());
return true;
}
};
// ========== USAGE ==========
int main() {
// Setup
CatalogService catalog;
BorrowService borrowService(catalog);
// Add items (Factory-like creation)
catalog.addItem(make_unique<Book>("B001", "Clean Code", "Robert Martin", "978-0132350884", 464));
catalog.addItem(make_unique<Book>("B002", "Design Patterns", "GoF", "978-0201633610", 395));
catalog.addItem(make_unique<DVD>("D001", "The Matrix", "Wachowskis", 136));
catalog.addItem(make_unique<Magazine>("M001", "IEEE Spectrum", "IEEE", 42));
// Create users
Student alice(1, "Alice", "alice@uni.edu");
Faculty bob(2, "Bob", "bob@uni.edu");
borrowService.registerUser(&alice);
borrowService.registerUser(&bob);
// Operations
cout << "=== Catalog ===" << endl;
catalog.listAll();
cout << "\n=== Borrowing ===" << endl;
borrowService.borrowItem(1, "B001"); // Alice borrows Clean Code
borrowService.borrowItem(2, "D001"); // Bob borrows The Matrix
borrowService.borrowItem(1, "B001"); // Fail — already borrowed
cout << "\n=== After Borrowing ===" << endl;
catalog.listAll();
cout << "\n=== Search ===" << endl;
auto results = catalog.search("Design");
for (const auto& id : results) {
cout << "Found: " << catalog.getItem(id)->getDetails() << endl;
}
cout << "\n=== Return ===" << endl;
borrowService.returnItem(1, "B001");
return 0;
}2. UML Class Diagrams
UML (Unified Modeling Language) is used to visualize class relationships.
BASIC NOTATION:
┌────────────────────┐
│ ClassName │ ← Class name
├────────────────────┤
│ - privateField │ ← Attributes
│ # protectedField │ - private
│ + publicField │ # protected
├────────────────────┤ + public
│ + publicMethod() │ ← Methods
│ - privateMethod() │
│ # protectedMethod()│
└────────────────────┘
RELATIONSHIPS:
──────────▷ Inheritance (IS-A)
class Dog : public Animal
Dog ──────────▷ Animal
─ ─ ─ ─ ─▷ Implements (Interface)
class Dog : public IAnimal
Dog ─ ─ ─ ─ ─▷ IAnimal
──────────► Association (uses/knows about)
class Car { Driver* driver; }
Car ──────────► Driver
◆────────── Composition (OWNS, part dies with whole)
class Car { Engine engine; }
Car ◆────────── Engine
◇────────── Aggregation (HAS, part can exist independently)
class Team { vector<Player*> players; }
Team ◇────────── Player
- - - - - > Dependency (temporarily uses)
void process(Logger& log) { log.write(...); }
Processor - - - - - > LoggerLibrary System UML
┌──────────────┐
│ <<interface>> │
│ IBorrowable │
├──────────────┤
│ +borrow() │
│ +returnItem()│
└──────┬───────┘
│ implements
┌──────────────┴──────────────┐
│ LibraryItem │
├─────────────────────────────┤
│ # id: string │
│ # title: string │
│ # available: bool │
├─────────────────────────────┤
│ + borrow(): bool │
│ + returnItem(): bool │
│ + getType(): string = 0 │
└──────┬──────────┬───────────┘
│ │
┌────────┘ └────────┐
┌─────┴────┐ ┌──────┐ ┌────────┴───┐
│ Book │ │ DVD │ │ Magazine │
├──────────┤ ├──────┤ ├────────────┤
│ -author │ │-dir │ │ -issue │
│ -isbn │ │-dur │ │ -publisher │
└──────────┘ └──────┘ └────────────┘3. Design a Parking Lot
A classic OOP design interview question.
// ========== ENUMS & TYPES ==========
enum class VehicleType { MOTORCYCLE, CAR, TRUCK };
enum class SpotSize { SMALL, MEDIUM, LARGE };
// ========== VEHICLES ==========
class Vehicle {
protected:
string licensePlate;
VehicleType type;
public:
Vehicle(string plate, VehicleType t) : licensePlate(plate), type(t) {}
string getPlate() const { return licensePlate; }
VehicleType getType() const { return type; }
virtual SpotSize requiredSpotSize() const = 0;
virtual ~Vehicle() = default;
};
class Motorcycle : public Vehicle {
public:
Motorcycle(string plate) : Vehicle(plate, VehicleType::MOTORCYCLE) {}
SpotSize requiredSpotSize() const override { return SpotSize::SMALL; }
};
class Car : public Vehicle {
public:
Car(string plate) : Vehicle(plate, VehicleType::CAR) {}
SpotSize requiredSpotSize() const override { return SpotSize::MEDIUM; }
};
class Truck : public Vehicle {
public:
Truck(string plate) : Vehicle(plate, VehicleType::TRUCK) {}
SpotSize requiredSpotSize() const override { return SpotSize::LARGE; }
};
// ========== PARKING SPOT ==========
class ParkingSpot {
string spotId;
SpotSize size;
Vehicle* parkedVehicle;
public:
ParkingSpot(string id, SpotSize s) : spotId(id), size(s), parkedVehicle(nullptr) {}
bool canFit(const Vehicle& v) const {
return !parkedVehicle && v.requiredSpotSize() <= size;
}
bool park(Vehicle& v) {
if (!canFit(v)) return false;
parkedVehicle = &v;
return true;
}
Vehicle* unpark() {
Vehicle* v = parkedVehicle;
parkedVehicle = nullptr;
return v;
}
bool isOccupied() const { return parkedVehicle != nullptr; }
string getId() const { return spotId; }
SpotSize getSize() const { return size; }
};
// ========== PARKING LOT ==========
class ParkingLot {
string name;
vector<unique_ptr<ParkingSpot>> spots;
map<string, ParkingSpot*> vehicleToSpot; // plate → spot
public:
ParkingLot(string n) : name(n) {}
void addSpot(string id, SpotSize size) {
spots.push_back(make_unique<ParkingSpot>(id, size));
}
ParkingSpot* findAvailableSpot(const Vehicle& v) {
for (auto& spot : spots) {
if (spot->canFit(v)) return spot.get();
}
return nullptr;
}
bool parkVehicle(Vehicle& v) {
if (vehicleToSpot.count(v.getPlate())) {
cout << v.getPlate() << " already parked!" << endl;
return false;
}
ParkingSpot* spot = findAvailableSpot(v);
if (!spot) {
cout << "No available spot for " << v.getPlate() << endl;
return false;
}
spot->park(v);
vehicleToSpot[v.getPlate()] = spot;
cout << v.getPlate() << " parked at " << spot->getId() << endl;
return true;
}
bool unparkVehicle(const string& plate) {
auto it = vehicleToSpot.find(plate);
if (it == vehicleToSpot.end()) return false;
it->second->unpark();
vehicleToSpot.erase(it);
cout << plate << " unparked" << endl;
return true;
}
int availableSpots() const {
return count_if(spots.begin(), spots.end(),
[](const auto& s) { return !s->isOccupied(); });
}
};4. Interview Questions
Fundamentals (Q1-Q15)
Q1: What are the four pillars of OOP? Encapsulation (bundle data + methods, restrict access), Abstraction (show only essentials, hide complexity), Inheritance (derive new classes from existing ones), Polymorphism (one interface, multiple behaviors).
Q2: Class vs Object? Class = blueprint/template defining structure and behavior. Object = instance of a class occupying actual memory. Class is defined once; objects can be created many times.
Q3: What is this pointer?
Implicit pointer to the current object in non-static member functions. Used for: resolving name conflicts, method chaining (return *this), passing current object to other functions.
Q4: Constructor vs Destructor? Constructor initializes objects (same name as class, no return type, can be overloaded). Destructor cleans up (prefixed with ~, no parameters, only one per class). Constructors called on creation; destructors on destruction.
Q5: What is a copy constructor?
A constructor that creates an object by copying another: MyClass(const MyClass& other). Default does shallow copy. Write custom for deep copy when class manages heap memory.
Q6: Shallow vs Deep copy? Shallow: copies pointer values (both objects share same memory). Deep: allocates new memory and copies actual data. Use deep copy when class has pointer members.
Q7: What is function overloading? Multiple functions with same name but different parameter types/count. Resolved at compile time (static polymorphism). Cannot overload by return type alone.
Q8: Struct vs Class in C++? Only difference: struct defaults to public access; class defaults to private. Convention: struct for POD data, class for encapsulated objects.
Q9: What is a virtual function?
A function declared with virtual keyword enabling runtime polymorphism. The correct version is called based on actual object type (not pointer type) via vtable/vptr mechanism.
Q10: What is a pure virtual function?
A virtual function with = 0: no implementation in base class, must be overridden by derived classes. Makes the class abstract.
Q11: Can constructor be virtual? No. The vtable doesn't exist during construction. Use Factory Method for "virtual construction" (virtual clone/create methods).
Q12: Why make destructor virtual? Without virtual destructor, deleting derived object through base pointer only calls base destructor — derived destructor skipped, causing resource leaks.
Q13: What is the Diamond Problem? When class D inherits from B and C, both inheriting from A — D gets TWO copies of A. Causes ambiguity. Solved with virtual inheritance.
Q14: What is object slicing? Assigning derived object to base by value — derived-specific data is lost. Polymorphism lost. Fix: use pointers/references.
Q15: Static vs Dynamic binding? Static (compile-time): non-virtual functions, function overloading. Dynamic (runtime): virtual functions, resolved via vtable. Static is faster; dynamic is more flexible.
Intermediate (Q16-Q35)
Q16: What is explicit keyword?
Prevents implicit type conversion via single-argument constructors. explicit Foo(int x) — can't do Foo f = 5;, must do Foo f(5);.
Q17: What is mutable keyword?
Allows a member to be modified in const member functions. Use for caches, mutexes, access counters.
Q18: What is a friend function/class? Grants access to private/protected members. Not a member of the class. Commonly used for operator overloading. Does not break encapsulation — it's explicitly granted.
Q19: What are access modifiers?
public: accessible everywhere. protected: accessible in class and derived classes. private: accessible only in the class. Default: private for class, public for struct.
Q20: What are static members?
Belong to the class, not instances. Static data: shared across all objects, defined outside class. Static functions: no this pointer, can't access non-static members.
Q21: What is const correctness? Marking methods const when they don't modify the object. Const objects can only call const methods. Read pointer declarations right-to-left.
Q22: What is operator overloading? Defining custom behavior for operators (+, -, ==, <<) with user types. Can't overload ::, ., .*, ?:, sizeof, typeid. Can't create new operators.
Q23: What is a vtable? Compiler-generated array of function pointers for virtual functions. One per class. Each object has a vptr pointing to its class's vtable. Enables runtime dispatch.
Q24: What is RTTI?
Runtime Type Information. typeid returns type info; dynamic_cast safely casts base to derived (returns nullptr if wrong type). Requires virtual functions.
Q25: dynamic_cast vs static_cast?
dynamic_cast: runtime check, safe for downcasting, returns nullptr on failure. static_cast: compile-time only, unsafe for downcasting. Always prefer dynamic_cast for downcasting.
Q26: What is an abstract class? Has at least one pure virtual function. Can't be instantiated. Can have constructors, data, and implemented methods. Used as base for derived classes.
Q27: Interface vs Abstract class? Interface: ALL pure virtual, no data (pure contract). Abstract: at least one pure virtual, can have data and implementation (partial template). C++ uses abstract classes for both.
Q28: What is multiple inheritance? A class inheriting from multiple base classes. C++ supports it; Java doesn't (for classes). Risks: diamond problem, ambiguity. Mitigate with virtual inheritance.
Q29: override vs final?
override: ensures function actually overrides a virtual function (compile-time check). final: prevents further overriding of a function or inheritance of a class.
Q30: What is method hiding? Non-virtual function in derived class with same name as base hides the base version. Through base pointer, base version is called. Different from overriding (virtual dispatch).
Q31: What is a functor?
Class that overloads operator(). Objects act like functions. Advantages over function pointers: can hold state, inlineable, type-safe. Lambdas are compiler-generated functors.
Q32: What are covariant return types?
Override can return a derived type of the base method's return type. Base::clone() returns Base*; Derived::clone() can return Derived*.
Q33: What is the Rule of 3/5/0? Rule of 3: define destructor, copy constructor, copy assignment (all or none). Rule of 5: add move constructor and move assignment. Rule of 0: use RAII types so compiler defaults work.
Q34: What is move semantics?
Transfer resources from temporary objects instead of copying. Move constructor takes T&& (rvalue reference). std::move casts to rvalue. Avoids expensive copies for temporaries.
Q35: What is a delegating constructor?
Constructor that calls another constructor of the same class: Foo() : Foo(0, 0) {}. Reduces code duplication.
Advanced / Design (Q36-Q50)
Q36: What are SOLID principles? S: Single Responsibility. O: Open/Closed. L: Liskov Substitution. I: Interface Segregation. D: Dependency Inversion. Five principles for maintainable, extensible OOP.
Q37: Explain SRP with example. A class should have one reason to change. Violation: Employee class that manages data, calculates pay, AND saves to database. Fix: split into Employee, PayCalculator, EmployeeRepository.
Q38: Explain OCP with example. Open for extension, closed for modification. Use interfaces/abstract classes. Adding a new notification type (Slack) shouldn't require modifying NotificationService — just add a new class implementing INotifier.
Q39: Explain LSP with example. Subtypes must substitute base types without breaking behavior. Rectangle-Square problem: Square overrides setWidth to also change height — breaks code expecting independent width/height.
Q40: Explain ISP with example. Don't force interfaces with methods clients don't use. Fat IMachine with print/scan/fax forces SimplePrinter to implement scan/fax. Fix: split into IPrinter, IScanner, IFaxer.
Q41: Explain DIP with example. Depend on abstractions, not concrete classes. Instead of UserService depending on MySQLDatabase directly, depend on IDatabase interface. Inject implementation via constructor.
Q42: What is Dependency Injection? Passing dependencies from outside instead of creating them inside the class. Constructor injection (preferred), setter injection, or method parameter. Enables testing with mocks.
Q43: Composition vs Inheritance? Composition: HAS-A (flexible, loose coupling, runtime swappable). Inheritance: IS-A (tight coupling, compile-time, polymorphism). Prefer composition. Use inheritance only for true IS-A with polymorphism.
Q44: What is the Singleton pattern? Ensures one instance with global access. Meyers' Singleton in C++: static local variable. Drawbacks: global state, hard to test, hidden dependencies.
Q45: What is Factory Method? Defines interface for creating objects; subclasses decide the type. Use when: type depends on config/environment, want to decouple creation from usage.
Q46: What is Observer pattern? One-to-many dependency: subject notifies observers on state change. Used in event systems, pub/sub, MVC. Subject maintains observer list, calls update() on changes.
Q47: What is Strategy pattern? Encapsulate algorithms in classes, make them interchangeable. Client holds interface; swaps implementations at runtime. Example: different sorting algorithms as strategy classes.
Q48: What is Decorator pattern? Add behavior dynamically by wrapping objects. Decorator implements same interface, delegates to wrapped object, adds behavior. Can stack multiple decorators.
Q49: What is RAII? Resource Acquisition Is Initialization. Resources acquired in constructor, released in destructor. C++'s mechanism for exception-safe resource management. smart pointers, lock_guard, fstream.
Q50: unique_ptr vs shared_ptr vs weak_ptr? unique_ptr: exclusive ownership, zero overhead, move-only. shared_ptr: reference-counted shared ownership, thread-safe counting. weak_ptr: non-owning observer, breaks cycles. Default to unique_ptr.
5. Coding Challenges
Challenge 1: Implement a Shape Hierarchy
Design a Shape hierarchy:
- Base class Shape with pure virtual area() and perimeter()
- Derived: Circle, Rectangle, Triangle
- Implement operator<< for printing
- Create a function that finds the shape with maximum area
from a vector<Shape*>Challenge 2: Design a Simple Observer
Implement a generic EventEmitter:
- subscribe(event, callback) — register a listener
- unsubscribe(event, callback) — remove a listener
- emit(event, data) — notify all listeners for that event
- Support multiple events and multiple listeners per eventChallenge 3: Implement a Strategy-Based Compressor
Design a file compressor using Strategy pattern:
- ICompressionStrategy with compress() and decompress()
- Implementations: ZipStrategy, GzipStrategy, NoCompression
- Compressor class that accepts any strategy
- Should be swappable at runtimeChallenge 4: Build a Command Pattern Calculator
Build a calculator with undo/redo:
- Commands: AddCommand, SubtractCommand, MultiplyCommand, DivideCommand
- CommandHistory with undo() and redo()
- Each command stores the operation and the operand
- Calculator displays current value after each operationChallenge 5: Design a Vending Machine (State Pattern)
Implement a vending machine with states:
- States: Idle, HasMoney, Dispensing, SoldOut
- Operations: insertMoney, selectItem, dispense, cancel
- Each state handles operations differently
- Use the State pattern to avoid giant if/else blocks6. Final Checklist
Go through this before your interview:
CONCEPTS:
[x] Can explain all 4 pillars with examples
[x] Know every constructor type and when to use each
[x] Understand vtable/vptr mechanism
[x] Can explain all inheritance types + diamond problem
[x] Know the difference between overloading, overriding, hiding
[x] Understand all SOLID principles with violation examples
[x] Know at least 5 design patterns cold
[x] Understand composition vs inheritance trade-offs
[x] Can explain RAII and smart pointers
[x] Know the Rule of 3/5/0
CODE:
[x] Can implement Singleton, Factory, Observer, Strategy from memory
[x] Can design a class hierarchy for any given problem
[x] Can write operator overloading for common operators
[x] Know when to use virtual vs non-virtual
[x] Can write deep copy and move constructors
[x] Understand and use const correctness
DESIGN:
[x] Can design a parking lot, library system, or elevator system
[x] Can draw basic UML class diagrams
[x] Know when to use inheritance vs composition
[x] Can identify SOLID violations in code
[x] Can refactor bad code to follow SOLID🎉 Congratulations!
You've completed the entire OOP curriculum! You now understand:
- ✅ Classes, Objects, Constructors, Destructors (from scratch)
- ✅ Encapsulation & Abstraction (data hiding, interfaces)
- ✅ Inheritance (all types, diamond problem, virtual inheritance)
- ✅ Polymorphism (compile-time + runtime, vtable internals)
- ✅ SOLID Principles (with real violations and fixes)
- ✅ Design Patterns (Singleton, Factory, Builder, Adapter, Decorator, Proxy, Observer, Strategy, Command)
- ✅ Advanced OOP (Composition, CRTP, RAII, Smart Pointers, DI)
- ✅ Real-World Design (Library System, Parking Lot, UML)
- ✅ 100+ Interview Questions with Answers
Go back to the Study Index and check off the interview readiness checklist. Good luck! 🚀