ByteWise

Section 4: Inheritance

Start With an Everyday Picture: Job Roles at a Company

Think about job titles at a trading firm. Every Employee has some things in common — a name, an employee ID, a salary, the ability to clock in. But a Trader is a specific kind of employee who also places trades and has a risk limit. An Analyst is also a specific kind of employee, but instead has research coverage areas and writes reports.

Notice the pattern: a Trader is an Employee — with extra stuff added on top. You wouldn't redefine "name, employee ID, salary" from scratch for Trader, Analyst, and every other role — you'd define those once on Employee, and let every role inherit them automatically.

That's inheritance: defining shared structure and behavior once, in a general class, and letting more specific classes automatically get all of it — while adding or changing only what's actually different about them.

💡🗣️ Conversational

Inheritance means a new class can say "I'm basically like this other class, plus a few extra things" — instead of copy-pasting all the shared code again.

Technical: Inheritance is a mechanism where a new class (the subclass / derived class) acquires the fields and methods of an existing class (the superclass / base class), and can add new fields/methods or override existing ones, without modifying the original class.

Why Bother — What Breaks Without It?

Imagine you don't use inheritance. You write MarketOrder with ticker, quantity, timestamp, and a validate() method. Then you write LimitOrder — but since there's no inheritance, you copy-paste ticker, quantity, timestamp, and validate() into it too. Then StopLossOrder — copy-paste again.

Now imagine a bug is found in validate() — say, it doesn't reject a negative quantity. You now have to find and fix that bug in three separate places, and hope you don't miss one. This is the exact same "change is expensive" problem from Section 0, just showing up between classes instead of within one class.

💡🔗 Ties Back to Section 0

With inheritance: validate() lives once, in a shared Order base class. Fix it once, every subclass gets the fix automatically.

The Code — Order Hierarchy (Stock/Trading Example)

Python:

class Order:
    def __init__(self, ticker, quantity):
        self.ticker = ticker
        self.quantity = quantity

    def validate(self):
        if self.quantity <= 0:
            raise ValueError("Quantity must be positive")

    def execute(self):
        print("Generic order executed")   # a default, can be overridden


class MarketOrder(Order):        # MarketOrder IS-A Order
    def execute(self):            # overriding the base version
        self.validate()
        print(f"Market order executed for {self.quantity} shares of {self.ticker}")


class LimitOrder(Order):
    def __init__(self, ticker, quantity, limit_price):
        super().__init__(ticker, quantity)   # reuse the parent's constructor
        self.limit_price = limit_price

    def execute(self):
        self.validate()
        print(f"Limit order executed for {self.quantity} shares of {self.ticker} at {self.limit_price}")
m = MarketOrder("AAPL", 10)
m.execute()   # uses validate() from Order — never redefined it here!

C++:

class Order {
protected:
    std::string ticker;
    int quantity;

public:
    Order(std::string t, int q) : ticker(t), quantity(q) {}

    void validate() {
        if (quantity <= 0) {
            throw std::invalid_argument("Quantity must be positive");
        }
    }

    virtual void execute() {
        std::cout << "Generic order executed" << std::endl;
    }

    virtual ~Order() {}
};

class MarketOrder : public Order {    // MarketOrder IS-A Order
public:
    MarketOrder(std::string t, int q) : Order(t, q) {}

    void execute() override {
        validate();
        std::cout << "Market order executed for " << quantity << " shares of " << ticker << std::endl;
    }
};

class LimitOrder : public Order {
private:
    double limitPrice;

public:
    LimitOrder(std::string t, int q, double lp) : Order(t, q), limitPrice(lp) {}

    void execute() override {
        validate();
        std::cout << "Limit order executed for " << quantity << " shares of " << ticker
                   << " at " << limitPrice << std::endl;
    }
};

Second Example — Account Hierarchy (Bank Example)

💡🗣️ Conversational Setup

A SavingsAccount and a CurrentAccount both need a balance, a withdraw method, a deposit method — but a SavingsAccount also earns interest, and a CurrentAccount allows going negative up to an overdraft limit.

Python:

class Account:
    def __init__(self, balance=0):
        self.__balance = balance

    def deposit(self, amount):
        self.__balance += amount

    def get_balance(self):
        return self.__balance


class SavingsAccount(Account):
    def __init__(self, balance=0, interest_rate=0.03):
        super().__init__(balance)
        self.interest_rate = interest_rate

    def add_interest(self):
        interest = self.get_balance() * self.interest_rate
        self.deposit(interest)
ℹ️🗣️ Notice

SavingsAccount never redefined deposit() or get_balance() — it just got them, for free, by inheriting from Account. It only added what's actually new: interest_rate and add_interest().

Key Vocabulary, Defined Plainly

TermMeaning
Base class / Superclass / Parent classThe general class being inherited from (Order, Account)
Derived class / Subclass / Child classThe specific class doing the inheriting (MarketOrder, SavingsAccount)
IS-A relationshipThe test for "should I use inheritance?" — ask: "is a MarketOrder truthfully a kind of Order?" Yes → inheritance fits. If the answer feels forced, it probably isn't a true IS-A.
OverridingA subclass provides its own version of a method the parent already defines (execute() in both MarketOrder and LimitOrder)
super() / Order(t, q)A way for the child's constructor to call the parent's constructor, so shared setup logic isn't duplicated

Connecting Back to Abstraction (Type 2) — How They Work Together

You've already seen Order used two ways: in Section 3 as an ABC/pure-virtual interface (no shared code, just a contract), and now as a base class with actual shared code (validate(), real fields). Both are real, common patterns:

  • Interface (abstract class, no implementation): use when subclasses genuinely have nothing in common except "they must be able to do X."
  • Base class with inheritance (shared code): use when subclasses share real, reusable logic (like validate()), plus each needs to override some specific behavior (execute()).

In practice, a base class can do both at once — provide real shared methods (validate()) and declare some methods as needing to be overridden, which is exactly what Order did above.

Line-by-line syntax differences:

WhatPythonC++Why different
Declaring inheritanceclass MarketOrder(Order):class MarketOrder : public Order {Both declare "IS-A." C++ requires the public keyword (there's also private/protected inheritance — rare, skip for now — public is what you'll use ~99% of the time).
Calling the parent's constructorsuper().__init__(ticker, quantity)Order(t, q) in the initializer listPython uses the super() function. C++ calls the parent constructor directly by name, in the child constructor's initializer list.
Overriding a methodJust redefine the method with the same name — no keyword neededvoid execute() override {override keyword recommendedPython has no compiler to check you actually overrode something real. C++'s override catches typos — if you misspell the parent method's name, the compiler errors instead of silently creating an unrelated new method.
Fields accessible to subclasses but not outsideN/A (Python doesn't truly enforce this — convention only, e.g. single underscore _ticker)protected: — a third access level between private and publicC++ has an explicit protected keyword: accessible inside the class and its subclasses, but not from outside code. Python has no compiler-enforced equivalent.
⚠️🧠 Check Yourself Before Moving On

In your own words — what's the IS-A test, and why does inheritance make bug-fixing cheaper? Use the validate() example.