ByteWise

Section 3: Abstraction

Start With an Everyday Picture: Driving a Car

When you drive a car, you turn the key (or push a button), press the accelerator, turn the steering wheel. That's it — that's everything you need to know to drive.

You have no idea how the engine actually combusts fuel, how the transmission shifts gears, how the power steering pump works. And you don't need to. The car hides all of that complexity behind a simple set of controls: key, pedal, wheel.

If you had to understand the entire engine to drive to the grocery store, nobody would ever drive.

That's abstraction: showing only what's necessary to use something, and hiding the complicated machinery behind it.

Turning the Analogy Into the Definition

💡🗣️ Conversational

Abstraction means designing a class so that it exposes a simple, clear way to use it — while hiding all the messy internal detail of how it actually works. The user of your class shouldn't need to know or care what's happening inside, only what they can do with it.

Technical: Abstraction is the process of exposing only the essential features and behavior of an object through a well-defined interface, while hiding the implementation details of how that behavior is actually carried out.

The Two Forms of Abstraction

Technical: Abstraction has two distinct mechanisms in practice, both serving the same goal (hide complexity, expose only what's needed), but operating at different levels:

  1. Abstraction within a class — hiding how a method does its work, while exposing a simple method signature to call it.
  2. Abstraction across classes — defining a contract (interface / abstract class) that says "any class of this kind must provide these behaviors," without saying how each one does it.
💡🗣️ Conversational

Think of it as two different questions:

  • Type 1 asks: "How do I hide the messy inside of one method, so the person calling it doesn't need to know the details?" That's what calculate_profit() does below — the caller doesn't see the brokerage fee formula.
  • Type 2 asks: "How do I make sure different classes all promise to support the same basic actions, even though each does it differently?" That's what Order / execute() does below — a MarketOrder and a LimitOrder both promise "I can execute myself," but how they execute is completely different.

The car analogy actually maps to Type 2 more precisely than the opening let on: every car — manual, automatic, electric — has the same interface (key/button, pedal, wheel), even though what happens under the hood is completely different between a gas engine and an electric motor. That's the interface/contract idea. Type 1 (hiding one method's internals) is more like: you don't need to know how the accelerator pedal translates your foot pressure into fuel injection timing — that's just one hidden mechanism inside one control.

Why both count as "Abstraction" and not two separate pillars: both are answering the same underlying question — "what does the outside world need to know, versus what can I hide from them?" Type 1 hides it inside one class's method. Type 2 hides it behind a shared contract that many classes fulfill differently. Same principle, applied at two different scopes.

How This Is Different From Encapsulation (People Mix These Up Constantly)

This trips almost everyone up at first, so let's be precise:

  • Encapsulation is about protecting data — stopping outside code from directly touching a field, forcing it through a method.
  • Abstraction is about hiding complexity — deciding what a method's user needs to see (its name, what it takes in, what it gives back) versus what they don't need to see (the actual logic inside).
ℹ️🗣️ Conversational Tie-Together

Encapsulation is the lock on the door. Abstraction is deciding what's on the other side of the door that people even need to know about. You can have a method be "public" (not encapsulated away) but still be an abstraction — because the person calling it doesn't see or care how it's implemented, only what it does.

This is also why the two get confused in practice: Type 1 abstraction (hiding a method's internal logic) often sits right next to encapsulation (hiding a field's data) in the same class — same class, same method, easy to blur together. They're still not the same thing: encapsulation is specifically about data protection, abstraction is about hiding complexity/detail — of which a hidden data field is just one example.

Why Bother — What Breaks Without It?

Imagine your withdraw() method from Section 2 didn't just check "amount > balance" — imagine it also had to: check with three different banking partner APIs, apply currency conversion, calculate interest accrued since midnight, log the transaction to five systems, and update a fraud-detection score.

If none of that were hidden — if the caller of withdraw() had to know and orchestrate all five of those steps themselves every time they wanted to take money out — every single place in your code that withdraws money would need to duplicate all that logic. One bug in currency conversion, and you're now fixing it in fifty different places.

💡🔗 Ties Back to Section 0

Abstraction says: the caller just calls withdraw(amount). Everything else is Account's problem, not theirs.

Type 1 — Abstraction Within a Class

Let's make the Stock example do something more realistic: calculating whether a trade is profitable — a genuinely complicated calculation the caller shouldn't need to know the internals of.

Python:

class Stock:
    def __init__(self, ticker, price):
        self.ticker = ticker
        self.__price = price

    def update_price(self, new_price):
        if new_price <= 0:
            raise ValueError("Price can't be zero or negative")
        self.__price = new_price

    def get_price(self):
        return self.__price

    def calculate_profit(self, buy_price, quantity):
        # complicated internals hidden from the caller:
        # - brokerage fee
        # - tax on capital gains
        # - currency conversion if foreign stock
        brokerage_fee = self.__price * quantity * 0.001
        gross_profit = (self.__price - buy_price) * quantity
        net_profit = gross_profit - brokerage_fee
        return net_profit

Using it — this is the whole point of abstraction:

apple_stock = Stock("AAPL", 200.0)
profit = apple_stock.calculate_profit(buy_price=190.0, quantity=10)
print(profit)   # caller has NO idea brokerage fees were even calculated

The caller just says "tell me my profit" — one line. They never see the brokerage fee formula, and if tomorrow the fee structure changes, or you add tax calculation, you change it in one place, and every caller automatically benefits without changing their code at all.

C++:

class Stock {
private:
    std::string ticker;
    double price;

public:
    Stock(std::string t, double p) : ticker(t), price(p) {}

    void updatePrice(double newPrice) {
        if (newPrice <= 0) {
            throw std::invalid_argument("Price can't be zero or negative");
        }
        price = newPrice;
    }

    double getPrice() const {
        return price;
    }

    double calculateProfit(double buyPrice, int quantity) const {
        double brokerageFee = price * quantity * 0.001;
        double grossProfit = (price - buyPrice) * quantity;
        return grossProfit - brokerageFee;
    }
};
Stock appleStock("AAPL", 200.0);
double profit = appleStock.calculateProfit(190.0, 10);   // same simplicity

Type 2 — Abstraction Across Classes (via Interfaces)

Everything above was Type 1 — abstraction within one class. This is the second form: defining a contract that says "any class of this type must provide these behaviors" — without saying how. This is what SOLID (🎯 Asked at Microsoft) and later design patterns actually lean on heavily, so it's worth introducing now.

💡🗣️ Conversational

Imagine you're designing a Trading System, and you know there will be multiple kinds of orders — Market Order, Limit Order, Stop-Loss Order. Instead of writing one giant method with if/else for every order type, you say: "every order type must know how to execute() itself — I don't care how, just that it can." That contract is called an interface (or abstract class).

Python (using ABC — Abstract Base Class):

from abc import ABC, abstractmethod

class Order(ABC):
    @abstractmethod
    def execute(self):
        pass   # no implementation here — just the contract

class MarketOrder(Order):
    def execute(self):
        print("Executing market order at current price")

class LimitOrder(Order):
    def __init__(self, limit_price):
        self.limit_price = limit_price

    def execute(self):
        print(f"Executing limit order at {self.limit_price}")

C++ (using a pure virtual function):

class Order {
public:
    virtual void execute() = 0;   // "= 0" means pure virtual — no implementation, it's a contract
    virtual ~Order() {}
};

class MarketOrder : public Order {
public:
    void execute() override {
        std::cout << "Executing market order at current price" << std::endl;
    }
};

class LimitOrder : public Order {
private:
    double limitPrice;
public:
    LimitOrder(double lp) : limitPrice(lp) {}
    void execute() override {
        std::cout << "Executing limit order at " << limitPrice << std::endl;
    }
};
ℹ️🗣️ Why This Matters for Change-Cheapness (Section 0 Again)

If tomorrow you need to add StopLossOrder, you just create a new class that implements execute(). You never touch MarketOrder or LimitOrder. This is Abstraction directly enabling that Open/Closed idea from Section 0 — new behavior via new code, not edited code.

Line-by-line syntax differences (new items only):

WhatPythonC++Why different
Declaring "this must be implemented, no default"@abstractmethod decorator + inherits from ABCvirtual void execute() = 0; — the = 0 marks it "pure virtual"Python needs the abc module bolted on since it's not a native language feature. C++ has this built directly into the language via virtual and = 0.
A class implementing the contractclass MarketOrder(Order):class MarketOrder : public Order {Both express "MarketOrder IS-A Order" — syntax differs but the relationship (inheritance, which we'll cover fully next) is the same idea.
Marking a method as fulfilling the contractNo special keyword needed (just define execute)override keyword (optional but strongly recommended)override in C++ tells the compiler "I intend to fulfill a parent's virtual method" — if you misspell the method name, the compiler catches it. Python has no such safety net.
⚠️🧠 Check Yourself Before Moving On

In your own words — what's the difference between Type 1 and Type 2 abstraction? Then explain how Abstraction differs from Encapsulation, using the car analogy plus one line from the Order/execute() example.


Note: The Two Forms of Abstraction — Clarified

Technical: Abstraction has two distinct mechanisms, both serving the same goal (hide complexity, expose only what's needed), but at different levels:

  1. Abstraction within a class (Type 1) — hiding how a method does its work, exposing only a simple method signature.
  2. Abstraction across classes (Type 2) — defining a contract (interface / abstract class) that says "any class of this kind must provide these behaviors," without saying how each one does it.
💡🗣️ Conversational
  • Type 1 asks: "How do I hide the messy inside of one method, so the caller doesn't need to know the details?"
  • Type 2 asks: "How do I make sure different classes all promise to support the same basic actions, even though each does it differently?"

Type 1 — How Does the Computer "Know" This Is Abstraction?

It doesn't. There's no keyword, no compiler check, no symbol for Type 1. Nothing stops you from writing the internal logic directly in the caller's code instead of inside a method — it would run identically. Type 1 abstraction is purely a discipline the programmer follows, not a language feature.

If you want to hide a formula or calculation from the outside world, you put that logic inside a function/method, and only the result is returned. The formula and intermediate steps stay hidden inside the function; the caller only ever sees: inputs in, answer out.

The test: could someone else use the method correctly without ever reading what's inside it? If yes — it's abstracted well.

Example (Stock — calculate_profit):

def calculate_profit(self, buy_price, quantity):
    brokerage_fee = self.__price * quantity * 0.001
    gross_profit = (self.__price - buy_price) * quantity
    return gross_profit - brokerage_fee
apple_stock = Stock("AAPL", 200.0)
profit = apple_stock.calculate_profit(buy_price=190.0, quantity=10)
# caller has NO idea brokerage fees were even calculated

Why self.__price but Not self.__buy_price?

This is a separate question from abstraction — it's about where each piece of data comes from, which is really Encapsulation showing up inside the method:

  • self.__price — belongs to the Stock object itself. It's the object's own private data, set when the object was created and stored permanently inside it. Accessed via self. because it lives inside the object.
  • buy_price — not stored in the object at all. It's a value the caller hands in just for this one calculation — a parameter, not the object's data. It only exists for the duration of this one method call, which is why it has no self..
💡🗣️ Analogy

self.__price is like the stock's permanent price tag that lives with it. buy_price is like a receipt you brought with you — it's your information, not the stock's, just shown to the method for this one calculation.

Where the Actual Abstraction Is Happening

Not in self.__price vs buy_price (that's encapsulation) — it's in the fact that the entire formula is wrapped in a method with a clear name, so the caller never has to write the formula themselves.

ℹ️Rule of Thumb

If you want to hide any formula or calculation from the outside, put that logic inside a function's body, and only return the value/answer to the public. The formulas and intermediate calculations remain hidden — the caller only ever interacts with the method's name, inputs, and returned result.

Payoff (ties back to why LLD cares about this at all): if the brokerage fee formula changes tomorrow, you change it in one place (calculate_profit), and every caller anywhere in the codebase automatically gets the new, correct behavior — without changing their own code.

Does Type 2 (Interface) Abstraction Require the Implementation to Stay Constant?

Technical: No. An interface fixes only the contract — the method names, their inputs, and their outputs (e.g., execute() on Order). It says nothing about how that method is implemented internally, and it doesn't need to. Each implementing class can have its own logic, and that logic can change freely over time, without breaking the contract or affecting any other class. The interface guarantees callers only ever depend on the contract, never on how any specific class fulfills it.

💡🗣️ Conversational

Think of MarketOrder, LimitOrder, and StopLossOrder as all promising "I can execute() myself" — that's the fixed part. But how each one executes is entirely up to that class, and can be rewritten however many times it needs to be. Change LimitOrder's internal logic tomorrow, and MarketOrder doesn't even notice — because nothing outside LimitOrder was ever relying on its internals, only on the fact that it has an execute() method.

ℹ️Rule

The contract (interface) is what stays stable — the implementation behind each class fulfilling that contract is free to evolve independently.