The Basics of Object-Oriented Programming, Revisited

OOP has been taught so mechanically for so long — “encapsulation, inheritance, polymorphism, abstraction,” memorized like a multiplication table — that a lot of developers can recite the four pillars without really understanding why any of them matter in real code.

Encapsulation: Hiding Implementation Details

class BankAccount:
    def __init__(self, balance=0):
        self._balance = balance  # convention: "protected", don't touch directly

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self._balance += amount

    def get_balance(self):
        return self._balance

The point isn’t hiding data for its own sake — it’s that deposit() can enforce the rule “no negative deposits” in exactly one place, instead of trusting every piece of calling code to remember to check.

Inheritance: Sharing Behavior, Carefully

class Vehicle:
    def __init__(self, make, model):
        self.make, self.model = make, model
    def describe(self):
        return f"{self.make} {self.model}"

class ElectricCar(Vehicle):
    def __init__(self, make, model, battery_kwh):
        super().__init__(make, model)
        self.battery_kwh = battery_kwh
    def describe(self):
        return f"{super().describe()} ({self.battery_kwh}kWh battery)"

Inheritance is easy to overuse — modern guidance generally favors composition (an object containing another) over deep inheritance hierarchies, which tend to become fragile as a codebase grows.

Polymorphism: Same Interface, Different Behavior

def print_description(vehicle: Vehicle):
    print(vehicle.describe())  # works for Vehicle OR any subclass

print_description(ElectricCar("Tesla", "Model 3", 75))

Why This Still Matters Alongside Functional Style

Modern codebases increasingly blend OOP with functional patterns rather than treating them as opposing philosophies — objects for stateful, identity-bearing things (a database connection, a user session), plain functions and immutable data for everything else. Knowing OOP well makes that blend a deliberate choice rather than a default.