OOP · Getting Started
Foundations: Why OOP Exists
Why object-oriented code exists — and what problem it actually solves.
Sign in to track your score
What you'll learn
- Explain why large procedural systems become hard to change.
- Break any real-world thing into state and behaviour.
- Tell a class apart from an object, and write both in C++.
- Name the four pillars and the problem each one addresses.
How to use this module. Wherever you see THINK, PREDICT or SPOT THE PROBLEM, stop and answer before reading on. The answer always follows immediately — cover it with your hand if you need to.
You start a small banking tool. Two variables, three functions, 40 lines. It works on the first try.
Three months later the same tool has 500 accounts, a freeze feature, transfers, monthly fees and a discount campaign. It is 3,000 lines across four files. Everything still compiles.
Then a customer's balance shows -300.
You open the code and ask the only question that matters:
Think — Somewhere in 3,000 lines, one line subtracted money without checking anything first.
How would you find out which line did it?
Take a second before reading on.
The honest answer is: you cannot, quickly. Any function, in any file, is allowed to write to that balance. Nothing in the code says who is permitted to change it, or under what conditions.
Key idea — Nothing here is a syntax problem. The code is correct and it compiles. The problem is organization — and that is the problem OOP was invented to solve.

The code style never changed. Only the number of connections did.
1.1 The mess OOP was invented to fix
What procedural programming actually means
- Data lives in variables.
- Work lives in functions.
- The program is a sequence of steps: do this, then that.
That is it. Data on one side, operations on the other.
Key idea — Procedural programming is not bad, and it is not outdated. Linux, SQLite and Git are largely procedural C, and they are excellent software. The challenge is organizing increasing complexity, not writing code.
Small program: this is genuinely good code
Problem: track one account's balance safely.
#include <iostream>
#include <string>
std::string owner = "Maya";
double balance = 500.0;
void deposit(double amount) {
if (amount <= 0) return;
balance += amount;
}
void withdraw(double amount) {
if (amount <= 0 || amount > balance) return;
balance -= amount;
}
int main() {
deposit(200);
withdraw(150);
std::cout << owner << ": " << balance << "\n"; // Maya: 550
}
What is happening: two variables hold the state, two functions enforce the rules.
Why it matters: you can read the whole thing on one screen. Exactly two functions touch balance, and both validate first. At this size, adding a class would add ceremony, not clarity.
Now the program grows
500 accounts means arrays. Same style, bigger scale.
std::string owners[100];
double balances[100];
bool isFrozen[100];
void deposit(int i, double amount) {
if (isFrozen[i] || amount <= 0) return;
balances[i] += amount;
}
// written later, in a different file, by a different person
void applyDiscount(int i) {
balances[i] -= 500;
}
Spot the problem — Both functions compile. Both look reasonable. What has quietly gone wrong in this design?
Answer — Four things, and none of them are syntax:
- One account is no longer one thing. Maya is now
owners[3],balances[3]andisFrozen[3]— three arrays held together by an index and by hope. - The index is the identity. Pass
jinstead ofiand money moves to the wrong customer. The compiler cannot help; it does not know these arrays are related. - The rules are duplicated. The
isFrozencheck must be repeated in every function that touches a balance. Miss one and you get a silent bug. - The data has no owner.
applyDiscountwrites tobalances[i]directly — no validation, no freeze check. Perfectly legal C++.
With an opening balance of 200, that one line produces -300.

Six functions, one shared pile of data, and no rule about who is allowed to change what.
Watch out — The tempting fix is "the developer should have been more careful." That does not scale to four engineers and 40,000 lines. Good design makes the wrong thing hard to write, instead of relying on everyone remembering.
Why adding more functions does not fix it
You could add a safeUpdateBalance() helper and tell the team to use it. But:
- That rule is a convention, not something the code enforces.
applyDiscount()can still writebalances[i] -= 500and compile cleanly.- More functions organize the work. The data stays exposed.
The question nobody has answered is still open: who is allowed to change this data, and under what conditions?
Key idea — So here is the idea that starts everything: What if the data and the behaviour that belong together could stay together?
1.2 Thinking in objects: data + behaviour together
Start with a car, not with code
Think — Two questions about a car parked outside:
- What does it know right now?
- What can it do?
You answered without effort:
| It Knows | It Does |
|---|---|
| speed = 0 | accelerate |
| fuel = 40 | brake |
| brand = "Aero" | refuel |
That is the whole model, and it works for every real thing — a phone, an order, a user account.
- What it knows → state → in code, data members
- What it does → behaviour → in code, methods

Two lists in the real world become one unit in code.
Key idea — OBJECT An object is a unit that holds its own data and the operations that work on that data, together in one place. Not a bag of variables. Not a group of functions. The pair.
The same idea in C++
Problem: model a car so that its data and its operations stay together.
class Car {
public:
std::string brand = "Aero"; // state
int speed = 0; // state
double fuel = 40.0; // state
void accelerate(int by) { speed += by; } // behaviour
void brake() { speed = 0; } // behaviour
void refuel(double l) { fuel += l; } // behaviour
};
What is happening:
class Car {... };defines a new type. The semicolon is required.public:means these members are reachable from outside. Access control gets its own module later.acceleratenever receivesspeedas a parameter —speedalready belongs to the object the method is running on.
Why it matters: in procedural style you would write accelerate(car1, 60) and pass the data in. Here you write car1.accelerate(60), because the data is already inside. Small syntax difference, large design difference.
Two objects, one class
Car car1;
Car car2;
car1.accelerate(60);
std::cout << car1.speed << " " << car2.speed;
Predict —
car1andcar2were created from the same class, and onlycar1accelerated. What does this print?
- A.
60 60- B.
60 0- C.
0 0- D. it will not compile
Answer — B, it prints 60 0 Each object gets its own copy of the data. car1.speed and car2.speed are separate memory. Changing one cannot affect the other.
But the method code is written once and shared by every object — creating 10,000 cars does not create 10,000 copies of accelerate().

Separate state, shared behaviour. This is the single most useful sentence in Module 1.
Procedural thinking vs object thinking
The real shift is not syntax. It is the question you ask first.
| Procedural Thinking | Object Thinking | |
|---|---|---|
| First question | "What functions do I need?" | "What things exist here?" |
| You start from | verbs | nouns |
| Data ends up | shared and exposed | owned by the thing it describes |
| Rules live | wherever someone wrote them | next to the data they protect |

Same requirements, two starting questions, two very different structures.
When you design a system, ask four things:
- What are the important entities?
- What data does each one hold?
- What can each one do?
- Which behaviour belongs to which entity?
Question 4 is where design actually happens. Calculating an order total needs the order's items — so calculateTotal() belongs to Order, not to Customer.
Class vs object
Now the distinction that beginners mix up most often.
- Class — the blueprint. Describes what every car will have. Costs no memory.
- Object — an actual instance built from that blueprint, with real values and real memory.
Remember — A house blueprint says "there will be three rooms." An actual house says "room one currently holds my desk." You cannot live in a blueprint.

One class, three objects, three independent sets of values.
Quick check — Which name is the class? How many objects exist? How many copies of
isOpenare in memory?
> class Door { public: bool isOpen = false; };
> Door front;
> Door back;
>
Answer — Door is the class. front and back are two objects. There are two copies of isOpen, one per object.
A useful language test: "a car has a speed" describes the class; "this car's speed is 60" describes an object.
Back to the -300 bug
Same banking problem, written as an object.
class BankAccount {
public:
std::string owner;
double balance = 0.0;
bool isFrozen = false;
void deposit(double amount) {
if (isFrozen || amount <= 0) return;
balance += amount;
}
bool withdraw(double amount) {
if (isFrozen || amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
};
What changed:
- Maya is now one object, not three array slots. The index-mismatch bug is structurally impossible.
- The freeze rule is written once, inside the methods that change the balance.
- A new rule ("balance may never drop below 100") means editing one method, not six files.
Watch out — One door is still open:
maya.balance = -300;would compile, because everything ispublic. Closing that door is encapsulation, and it gets its own module. Module 1's job was to get the data and its rules into the same place first.
1.3 The four pillars, in one picture
This section is a map, not a lesson. Each pillar has a dedicated module later. Your goal here is to know that these four ideas exist and which problem each one addresses.

Encapsulation
Keep an object's data together with the operations that manage it, and control how that data is accessed or changed.
Real life: an ATM holds the cash and the rules — correct PIN, sufficient balance, daily limit. You cannot reach past the interface and take the cash directly.
The problem it addresses: shared data with no owner, exactly like the -300 bug. When access is controlled, the invalid write does not merely get caught — it stops being writable.
Abstraction
Expose what someone needs in order to use the thing, and hide the implementation complexity behind it.
Real life: you press the brake pedal and the car slows down. Hydraulic pressure, pad friction and ABS sensors are real, and you do not need to know any of them.
The problem it addresses: callers drowning in detail. When order.place() hides eight internal steps, those steps can change completely without touching the code that calls it.
Inheritance
Build a new type from an existing related type, so the new type extends the characteristics and behaviour of the original.
Real life: a savings account is a bank account — it has everything an account has, plus interest. That "is-a" relationship is the point.
The problem it addresses: modelling related types honestly. Reusing code is a benefit, not the definition; if the "is-a" relationship is not true, the design is wrong even when the reuse works.
Polymorphism
Let one interface produce different behaviour depending on which object is actually involved.
Real life: one Pay button on a checkout screen. Card, wallet and bank transfer each do something completely different behind it, and you never have to know which one ran.
The problem it addresses: branching that grows forever. Without it, every new payment type means another if in every place that touches payment. With it, the caller keeps one line and new types slot in behind the same interface.
Remember — Each of these ideas deserves its own deep dive, which we will cover in dedicated modules. Right now you only need to be able to say what problem each one is for.
How the three topics connect

- Procedural code is fine small, but as it grows the data spreads out and loses its owner.
- Adding more functions organizes the work, not the data — so the problem survives.
- Changing the starting question from "what functions?" to "what things?" changes the structure.
- Each thing then keeps its own data and its own rules: that unit is an object.
- A class is the design for that unit; an object is an actual instance of it.
- The four pillars are what make this approach hold up at large scale.
- Every step exists because of the problem created by the step before it.
Common Misconceptions
❌ "OOP means using classes everywhere."
✅ OOP is a tool for managing complexity. A 60-line script or a pure math utility is clearer without classes.
💡 Tutorials present OOP as the "modern" way, which implies the alternative is wrong.
❌ "Procedural programming is bad."
✅ It is excellent for many problems, and the code inside every method is still procedural.
💡 People frame it as old vs new, when it is really small-scale vs large-scale.
❌ "A class and an object are the same thing."
✅ A class is a design and costs no memory. An object is an instance with real values and real memory.
💡 Everyday speech uses one word for both: "the Car has a speed" vs "this car's speed is 60."
❌ "Encapsulation just means making variables private."
✅ private is one technique. The idea is that data and the rules governing it form one unit with controlled access.
💡 It is commonly memorized as "data hiding," which is a consequence, not the definition.
❌ "Inheritance is for code reuse."
✅ Inheritance models an "is-a" relationship. Reuse is a benefit. When you only want reuse, composition is usually the better tool.
💡 The first example everyone is shown highlights "look, we did not rewrite the code."
❌ "Every real-world noun should become a class."
✅ A class earns its place when it has both data and behaviour. Colour, Speed and Name are usually just values.
💡 "Noun equals class" is a useful starting heuristic, not a rule.
❌ "OOP automatically makes code better or faster."
✅ It targets maintainability, not speed. Extra indirection can make OOP code slightly slower.
💡 "Better designed" gets read as "better in every dimension."
❌ "All four pillars must always be used together."
✅ They are independent tools. Plenty of well-designed classes use encapsulation and abstraction and never involve inheritance.
💡 They are always taught as a set of four, which suggests they ship as a bundle.
Interview Corner
Q1 (Easy). What is the difference between a class and an object? A class is a blueprint describing what data and behaviour a type will have; it occupies no memory by itself. An object is an instance created from that class, with its own values and its own memory. One class can produce any number of objects, each with independent state.
Q2 (Easy). What do "state" and "behaviour" mean? State is the data an object currently holds, such as balance = 500. Behaviour is what the object can do, such as withdraw(). An object is state and behaviour packaged as one unit.
Q3 (Easy). What is the core difference between procedural and object-oriented design? In procedural design, data and functions are separate and data is passed into functions. In OOP, data and the operations on it live inside the same unit. The deeper difference is the first question you ask: "what functions do I need?" versus "what things exist, what do they hold, and what can they do?"
Q4 (Medium). If you create 1,000 objects of a class, are there 1,000 copies of its methods in memory? No. Each object gets its own copy of the data members, but the method code exists once and is shared. The compiler arranges for each call to operate on the correct object. So object count scales data memory, not code size.
Q5 (Medium). Does OOP make programs faster? No — that is a common misconception. OOP targets maintainability and organization. Some OOP-heavy code is marginally slower due to indirection. The payoff is that changing the system six months later is safe, and on large projects engineering time costs more than CPU time.
Q6 (Conceptual). What is the real benefit of keeping data and behaviour together? It puts responsibility in one place. The rule is written once instead of duplicated, changing the rule means editing one location, and when the data becomes invalid you know where to look. Procedural code offers none of those three guarantees, because any function can modify the data.
Q7 (Conceptual). How are encapsulation and abstraction different? Abstraction is a design-level decision about what the outside world needs to see. Encapsulation is the implementation-level guarantee that data and its rules form one unit with controlled access. In one line: abstraction decides what is visible; encapsulation protects what is not.
Q8 (Tricky). Give an example where OOP is the wrong choice. A one-off script that reads a CSV and prints a total, a pure mathematical routine such as matrix multiplication, or performance-critical embedded code needing exact control over memory layout. In all three, classes add structure without adding clarity. OOP earns its cost when a system has many entities, each with its own state that changes over time.
30-Second Recap
| Concept | One Line |
|---|---|
| Why OOP exists | Large procedural systems spread their data out until no one owns it, and every change becomes risky |
| Object | A unit holding its own data plus the operations that work on that data |
| State | What the object knows right now — speed = 60 |
| Behaviour | What the object can do — accelerate() |
| Class vs object | Class = blueprint, no memory. Object = instance, real memory, real values |
| Four pillars | Encapsulation, Abstraction, Inheritance, Polymorphism |
If you remember only 5 things
- The problem was never syntax — it was organization. Procedural code does not break as it grows; it spreads out.
- An object is data plus the behaviour that belongs to that data, kept in one place.
- State is what it knows. Behaviour is what it does.
- Every object owns its data; the method code is shared by all objects of the class.
- Change the first question. Not "what functions do I need?" but "what things exist, what do they hold, and what can they do?"
- 1.
class Counter { public: int value = 0; void increment() { value++; } }; int main() { Counter a, b; a.increment(); a.increment(); b.increment(); std::cout << a.value << " " << b.value; }What does this print?
- 2.
class Playlist { public: std::string title; int songCount = 0; void addSong() { songCount++; } };Which statement is correct?
- 3.
class Door { public: bool isOpen = false; void open() { isOpen = true; } }; int main() { Door front; Door back; front.open(); std::cout << front.isOpen << " " << back.isOpen; }How many objects exist, and what does this print?
- 4.
The problem
Design the basic structure of a food delivery application. It needs to let a customer browse restaurants, add items to an order, place the order, pay for it, and have a delivery partner deliver it.
You are not writing a full system. You are identifying structure.
Work through it in four steps:
- Identify the objects that exist in this system.
- List the state each object holds.
- List the behaviour each object performs.
- Note what does not belong to a given object, and say why.
Hint — Describe the system in one plain sentence and circle the nouns: "A customer picks items from a restaurant's menu, creates an order, makes a payment, and a delivery partner delivers it."
Then for every piece of data ask: if this thing disappeared, who would still need this data? That is its owner.
Possible solution
| Object | State | Behaviour |
|---|---|---|
MenuItem | name, price, isVegetarian | priceWithTax() |
Restaurant | name, address, rating, menu, isOpen | addMenuItem(), search() |
Customer | name, phone, address, walletBalance | placeOrder(), updateAddress() |
Order | orderId, customer, restaurant, items, status, total | calculateTotal(), updateStatus(), cancel() |
Payment | paymentId, amount, method, status | process(), refund() |
DeliveryPartner | name, vehicleType, currentLocation, isAvailable | acceptOrder(), markDelivered() |
What does not belong where:
orderTotaldoes not belong toCustomer— it describes one specific order, not the person.availableSeats-style live data does not belong to menu items — it changes per order, not per item.