The four pillars of OOP, actually explained

Encapsulation, abstraction, inheritance, and polymorphism — the four words every OOP explanation lists, made concrete instead of memorized as a checklist.

Beginner

3 min read

Why these four, specifically

Every introduction to object-oriented programming lists the same four concepts, usually as a bullet list to memorize. They're worth more than that: each one is a real, practical answer to a real problem that comes up naturally once you start building anything with classes. Seeing the problem each one solves is what makes them stick, instead of being four words recited without understanding why they matter.

Encapsulation: bundling data with the code that manages it

class BankAccount:
    def __init__(self, balance):
        self._balance = balance
 
    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("deposit amount must be positive")
        self._balance += amount
 
    def get_balance(self):
        return self._balance

Encapsulation means keeping an object's data together with the methods that are allowed to change it, and controlling access so the data can't be modified in ways that break the object's own rules. _balance (the leading underscore is Python's convention for "treat this as internal, not part of the public interface") is only ever changed through deposit(), which enforces "you can't deposit a negative amount." If balance were just a plain public attribute anyone could set directly (account.balance = -500), nothing would stop an invalid state from happening. Encapsulation is what makes an object's own rules actually enforceable.

Abstraction: hiding how something works, exposing only what it does

account.deposit(100)   # you don't need to know HOW balance is stored or validated

Abstraction means exposing a simple interface (deposit(amount)) while hiding the implementation details behind it (how the balance is actually stored, what validation runs, whether it writes to a database). Calling .deposit(100) doesn't require knowing any of that — the complexity is hidden behind a name that describes what it does, not how. This is closely related to encapsulation but is a different concept: encapsulation is the mechanism (bundling + access control), abstraction is the goal (a simple interface that hides real complexity).

Inheritance: reusing and extending behavior from an existing class

class SavingsAccount(BankAccount):
    def __init__(self, balance, interest_rate):
        super().__init__(balance)
        self.interest_rate = interest_rate
 
    def apply_interest(self):
        self.deposit(self._balance * self.interest_rate)

Inheritance means a new class (SavingsAccount) can reuse everything an existing class (BankAccount) already defines — the deposit logic and its validation rules — while adding what's specific to it (interest_rate, apply_interest). This is the mechanism covered in depth in its own lesson; here, the point is just recognizing it as one of the four core ideas, solving the specific problem of "I need something almost like this existing class, plus a bit more."

Polymorphism: the same call, correct behavior for whatever type it actually is

accounts = [BankAccount(100), SavingsAccount(500, 0.02)]
 
for account in accounts:
    print(account.get_balance())   # works correctly for both, no type-checking needed

Polymorphism means code written against a general type (BankAccount) works correctly with any of its subtypes (SavingsAccount) without needing to check which specific one it's actually dealing with. The loop above doesn't need if isinstance(account, SavingsAccount): ... — calling .get_balance() just works, correctly, for whichever kind of account it happens to be. This is the practical payoff of inheritance done well: write code once against the general shape, and it correctly handles every specific variant of that shape.

How the four actually relate to each other

These aren't four unrelated rules — they build on each other in practice. Encapsulation is the mechanism that makes an object trustworthy (its data can't be corrupted from outside). Abstraction is the resulting benefit (callers get a simple interface, not implementation detail). Inheritance is how related classes share and extend behavior. Polymorphism is what makes that shared behavior actually useful — code written once, working correctly across every related type. Seeing them as a connected chain, not four separate flashcard definitions, is what turns "I can define these words" into "I can recognize when a design is actually using them well."

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What problem does encapsulation actually solve?

2. How is abstraction different from encapsulation, even though they're closely related?

3. What is polymorphism, in one sentence?

4. How do the four pillars actually relate to each other, rather than being four separate rules?