Skip to content

Composition or inheritance: has-a or is-a

How to choose between a class that holds another object and a class that is a kind of another class, why inheritance can multiply your classes, and the one place where inheritance is a good fit.

  • Lesson 12 of 169
  • 21 min read
  • Core
  • Free to read · No account needed
On this page
  1. Why interviewers ask this
  2. The idea in plain words
  3. Pause and predict
  4. Worked example
  5. Trade-offs
  6. Common misconceptions
  7. How to say it in the interview
  8. Check your understanding
  9. Answers
  10. Summary card: composition or inheritance
  11. Sources

Before you start

This lesson uses ideas from these earlier lessons. Read them first if any idea is new to you.

01Why interviewers ask this

When you build a class, you must decide how it reuses another class. There are two ways. The new class can be a kind of the old class, which is called inheritance. Or the new class can hold the old class as a part. Interviewers ask about the choice often: "Would you use inheritance here?" The answer shows whether you can think about change.

A weak answer uses inheritance everywhere, because it feels like reuse. After the second new requirement the classes multiply. A change in the old class then breaks the new ones in ways nobody expected. A strong answer asks two questions first and picks the lighter tool. It can also name the case where inheritance is the better tool.

At SDE-1 level you should know the difference and use inheritance for real "kind of" links. At SDE-2 level you should explain how inheritance multiplies classes and how holding an object avoids it.

02The idea in plain words

A samosa is a kind of snack. A canteen bill has a way of paying. The first is an "is a" link. The second is a "has a" link. A bill is not a kind of payment, and a payment is not a kind of bill. They are two separate things that work together.

This gives the test. Ask: "Is it a kind of that?" If yes, inheritance may fit. Ask: "Does it have one?" If yes, hold it as a field. When unsure, hold it.

Parent, child, override and extend

Inheritance lets one class take everything from another. The class you take from is the parent class. The class that takes is the child class. You write the parent in brackets: class Samosa(Snack):. The child has every field and method of the parent.

A child can change a method in two ways. To override a method is to write a new version that replaces the parent's version. To extend a method is to write a new version that first calls the parent's version and then adds more. The call is super().label(), where super() means "the parent's part of this object".

python
class Snack:
    def label(self):
        return "snack"

class Samosa(Snack):
    def label(self):
        return super().label() + ": samosa"

print(Samosa().label())

This prints snack: samosa. Inheritance is a strong link. The child depends on the parent. If someone changes the parent, every child can break. A child can also be unable to keep a promise that its parent makes. A later section shows this.

Subclass explosion

Our canteen has two things that differ. The first is the member kind. A student gets 10 percent off. A staff member gets Rs 30 off when the bill is at least Rs 100. A guest gets nothing. Here Student is a kind of member, not the card type of the last lesson. The second is the way to pay: card, UPI or cash.

With inheritance you show a difference by making a child class. To show both differences you need one child class for each pair. The symbol // divides and drops the part after the point: 140 * 10 // 100 is 14.

python
class BaseOrder:
    def __init__(self, total):
        self.total = total

class StudentCardOrder(BaseOrder):
    def due(self):
        return self.total - self.total * 10 // 100
    def pay(self, balance):
        return self.due() <= balance

class StudentUpiOrder(BaseOrder):
    def due(self):
        return self.total - self.total * 10 // 100
    def pay(self, limit_left):
        return self.due() <= limit_left

These are two of the classes we would need. The discount rule for students is copied into every student class. This is subclass explosion: two ideas that vary give kinds x ways classes, and every new way adds one class for each kind. You could put the student rule into a StudentOrder between the two levels. You would still need a class for every pair.

Composition and delegation

Composition means that a class holds other objects as fields and uses them. To delegate is to ask the held object to do the work. The order holds a member and a payment method. It asks the member for the discount and asks the payment method to pay. The two ideas now vary on their own, in their own classes. You can put in any part.

We use PaymentMethod, CardPayment, UpiPayment, CashPayment and VoucherPayment from the last lesson, and the CanteenCard of the lesson before it. The new part is the member. It has one interface and one small class for each kind.

python
from abc import ABC, abstractmethod

class Member(ABC):
    @abstractmethod
    def discount(self, total):
        """Return the rupees taken off total. Never more than total."""

class Student(Member):
    def discount(self, total):
        return total * 10 // 100

Staff members and guests follow the same pattern.

python
class Staff(Member):
    def discount(self, total):
        if total >= 100:
            return 30
        return 0

class Guest(Member):
    def discount(self, total):
        return 0

Now the order that holds both parts.

python
class Order:
    def __init__(self, total, member, payment):
        self._total = total
        self._member = member
        self._payment = payment

    def due(self):
        return self._total - self._member.discount(self._total)

    def settle(self):
        due = self.due()
        if self._payment.pay(due):
            return f"paid Rs {due}"
        return f"refused Rs {due}"

Look at the two links. Student is a kind of Member, so inheritance is right there. Order is not a kind of member or payment method. It has one of each.

A child must work wherever its parent works

Here is the plain rule for inheritance. Code that works with the parent must also work with every child, with no surprise. If a child cannot do what its parent promises, the link is wrong. See a child that breaks the promise of PaymentMethod:

python
class LeakyPayment(PaymentMethod):
    def __init__(self, balance):
        self._balance = balance

    def pay(self, amount):
        if amount > self._balance:
            raise ValueError("not enough balance")
        self._balance -= amount
        return True

The promise says "do not raise an error for a refusal". This child raises. Order.settle does not expect that, so it crashes. Before you make a child, ask whether it can keep every promise of the parent.

Depth and many parents

Keep inheritance with few levels. A chain of parent, child and grandchild is already a warning. Two levels are usually enough. This is a common guideline, not a rule of the language. Python also lets a class have many parents: class A(B, C). A mixin is a small class with a few helper methods that you add this way. Python must decide which parent to search first, and with many parents that order is hard to follow. In a round, avoid many parents. In Java, a class may have only one parent class (but many interfaces), so this problem does not arise there.

A good use: Template Method

There is one use of inheritance that fits very well. The parent writes a method with fixed steps, in a fixed order. One step is left abstract. Each child fills that one step. This is called Template Method. The parent writes the order, and a child fills only one step, so a child has no reason to touch the order. (In Java you can write final to forbid a change. Python relies on the habits of the team.) In a text, "\n" is a line break.

python
class Bill(ABC):
    def render(self, lines, total):
        text = self.header() + "\n"
        for line in lines:
            text += line + "\n"
        return text + f"Total Rs {total}\n" + self.footer()

    def header(self):
        return "CANTEEN BILL"

    @abstractmethod
    def footer(self):
        """The one step each child fills in."""

class StudentBill(Bill):
    def footer(self):
        return "Student discount 10% applied"

render fixes the steps: the header, the lines, the total, the footer. StudentBill fills the footer only.

Want to check your understanding? Try explaining this idea in your own words. You can continue reading first, or ask the AI tutor a question. Sign up free to ask the tutor

03Pause and predict

Pause. Guess first.

The canteen has 3 member kinds and 3 ways to pay. With one class for every pair, how many classes are there? A voucher is added as a fourth way to pay. How many new classes are needed with pairs, and how many if an order holds a member and a payment method?

Write your guess before you read on. The answer is in the worked example.

04Worked example

Counting the classes.

DesignAt the startAfter the voucher
One class for every pair3 x 3 = 99 + 3 = 12
Order holds a member and a payment method3 + 3 = 66 + 1 = 7

With pairs, the voucher adds one class for each member kind: 3 new classes. With composition it adds 1 payment class. This answers the predict box: 3 new classes against 1. The gap grows. With 5 kinds and 5 ways, pairs give 5 x 5 = 25 classes and composition gives 5 + 5 = 10.

The class Order has a Member and has a PaymentMethod, shown by two arrows labelled has a.
The order holds the parts, so the classes add up instead of multiplying.
The class Order has a Member and has a PaymentMethod, shown by two arrows labelled has a. Student, Staff and Guest are kinds of Member. CardPayment, UpiPayment, CashPayment and VoucherPayment are kinds of PaymentMethod. That is 3 plus 4, which is 7 classes. One class for every pair would need 3 times 4, which is 12.

Running the composed design. Assumption: the bill is Rs 140. The student card holds Rs 200, the UPI payment has Rs 100 of its limit left, and the cash given is Rs 150. Run with Python 3.11.

python
card = CanteenCard("C101", CardType.STUDENT, 200)
print(Order(140, Student(), CardPayment(card)).settle(), card.balance)
print(Order(140, Staff(), UpiPayment(100)).settle())
print(Order(140, Guest(), CashPayment(150)).settle())

It prints paid Rs 126 74, then refused Rs 110, then paid Rs 140. The arithmetic:

  • Student and card: the discount is 140 x 10 / 100 = Rs 14, so the amount due is 140 - 14 = Rs 126. The card goes from 200 to 200 - 126 = Rs 74.
  • Staff and UPI: the bill is at least 100, so the discount is Rs 30. The amount due is 140 - 30 = Rs 110. This is more than the UPI limit of Rs 100, so it is refused.
  • Guest and cash: no discount, so the amount due is Rs 140. The cash given, Rs 150, is enough.

The voucher. It is a payment class from the last lesson, and it works with all three member kinds, because Order does not change.

python
print(Order(140, Student(), VoucherPayment(150)).settle())

It prints paid Rs 126: 126 is not more than 150.

The broken promise. Use the leaky class in the same order:

python
try:
    print(Order(140, Guest(), LeakyPayment(100)).settle())
except ValueError as error:
    print("crash:", error)

It prints crash: not enough balance. The amount due is Rs 140, the balance is Rs 100, and the child raised where the parent promised not to.

Template Method.

python
print(StudentBill().render(["Thali Rs 90", "Lassi Rs 50"], 126))

It prints five lines: CANTEEN BILL, Thali Rs 90, Lassi Rs 50, Total Rs 126 and Student discount 10% applied.

05Trade-offs

Composition solves the explosion: classes add up, each part changes on its own, and you can put in any part, even in a test. It costs a few more lines, because the order must pass each request to the part, and the reader must follow one more object. Inheritance solves repeated code for real "kind of" links and gives Template Method. It costs a strong link: a change in the parent can break every child. I would change to inheritance in one case. The child must be truly a kind of the parent, keep all its promises, and the parent must fix a sequence of steps. If kinds differ only by a number, I would store the number in a field and write no new class.

06Common misconceptions

"Inheritance is the main way to reuse code." It is one way, and a strong link. Holding an object reuses the same code without tying your class to a parent.

"A child is always safe, because it is a kind of its parent." The words can say yes while the code says no. LeakyPayment is a payment method by name, but it breaks the promise and crashes the order.

"Prefer composition means never use inheritance." It means start with the lighter tool. Interfaces and Template Method are good uses of inheritance.

07How to say it in the interview

Say it like this

I ask whether it is a kind of that or has one. When two things vary, I make the order hold both parts, so the classes add up instead of multiplying.

08Check your understanding

  1. Use the test "is it a kind of that, or does it have one?" on three pairs. (a) Samosa and Snack. (b) Order and Member. (c) Order and PaymentMethod.
  2. A canteen has 4 member kinds and 2 ways to pay. How many classes do you need (a) with one class for every pair, and (b) with composition? A fifth member kind is added. How many new classes does each design need?
  3. A teammate writes class CashPayment(CardPayment) to reuse a balance check, and overrides pay. What is wrong, and what do you do?

09Answers

Show the answers
  1. (a) A samosa is a kind of snack: inheritance fits. (b) An order has a member: hold it. (c) An order has a payment method: hold it.
  2. (a) Pairs: 4 x 2 = 8 classes. (b) Composition: 4 + 2 = 6 classes. A fifth member kind adds one class for each way to pay with pairs: 2 new classes, so 10. With composition it adds 1 class, so 7.
  3. Cash is not a kind of card. CashPayment carries card fields it does not use, and a change in CardPayment can break it. Make both implement PaymentMethod. If both need the same check, put it in a small helper that both call.

10Summary card: composition or inheritance

One-page summary

  • Test. "Is it a kind of that?" means inheritance may fit. "Does it have one?" means hold it as a field. When unsure, hold it.
  • Words. Override replaces a parent method. Extend calls super() and adds more. Delegate means ask the held part to do the work.
  • Explosion. Two ideas that vary give kinds x ways classes with inheritance, and kinds + ways with composition. Example: 3 x 3 = 9 against 3 + 3 = 6.
  • Rule. A child must work wherever its parent works. Do not break the parent's promise.
  • Depth. Two levels is a warning. Avoid many parents and mixins.
  • Good use. Template Method: the parent fixes the steps, the child fills one step.
  • Trap. Using inheritance only to reuse code.

11Sources

  • super(), many parents and the order in which Python searches them: Python tutorial, section 9.5 "Inheritance", https://docs.python.org/3/tutorial/classes.html (opens in a new tab) (checked 29 Oct 2026).
  • Template Method (the parent fixes the steps and the children fill some of them): Refactoring Guru, https://refactoring.guru/design-patterns/template-method (opens in a new tab) (checked 29 Oct 2026). The name comes from the book "Design Patterns" by Gamma, Helm, Johnson and Vlissides (1994).
  • The advice that two levels of inheritance are usually enough, and that mixins are best avoided in a round, is a common guideline and not from a source.
  • The canteen, the prices, the discounts and the limits are made up for teaching. All code was run with Python 3.11.

Try what you just learned

Put this idea into practice with a short exercise or a relevant tool. Free to try. No sign-up needed.

More practice. This needs a free account.

Continue learning with the lessons that build on this one.

Practise this idea

Have a question about this lesson?

Ask the AI tutor to explain a difficult idea, walk through an example or check your understanding. It uses the lesson to help you work through the topic.

A free account includes 10 AI tutor questions a day. The lesson remains free to read without an account.

All lessons in Module 2: Low-level design and machine coding

Prefer another topic? Browse all 169 lessons.