← Open in the full interactive course (progress tracking, search & more)

Inheritance says "I am a..." — Composition says "I have a..."

Imagine you're building a car. You could use inheritance: "A Car is a Vehicle" — that works. But a Car also has an engine, wheels, and a steering wheel. If you tried to model those with inheritance, you'd need a messy hierarchy: "Car is a Engine"? No, that's nonsense.

Inheritance is a powerful tool, but it's often overused. When you inherit, you tie your subclass tightly to the parent. That's fine for "is-a" relationships (like Dog is an Animal), but for "has-a" relationships (like a Car has an Engine), composition is the better choice.

In this lesson, you'll learn when to use inheritance, when to use composition, and why composition is often preferred in modern software design.

What Is It?

The Simple Explanation

Inheritance is the "is-a" relationship. A Dog is an Animal. A Square is a Shape. The subclass inherits behavior from the base class.

Composition is the "has-a" relationship. A Car has an Engine. A House has a Door. The class contains instances of other classes to provide functionality.

The Technical Definition

Inheritance (in OOP) is a mechanism where a class derives from another class, gaining its members and behavior. In C#, inheritance is single (a class can inherit from only one base class) but allows method overriding and polymorphism.

Composition is a design principle where a class contains objects of other classes as fields or properties. It delegates work to those contained objects, effectively "composing" behavior from multiple parts.

Inheritance (is-a)

Composition (has-a)

Why Does It Exist?

The Problem with Overusing Inheritance

Inheritance sounds great — "reuse code!" — but it comes with hidden costs:

The Solution — Prefer Composition

Composition solves these issues:

The principle "Favor composition over inheritance" is one of the most important design guidelines in object-oriented programming.

Big Picture

INHERITANCE vs COMPOSITION — VISUAL
Inheritance
AnimalMammalDog
Dog is a Mammal is a Animal
Fixed hierarchy
Composition
Car Engine
Car Wheel
Car SteeringWheel
Car has an Engine, Wheels, SteeringWheel
Flexible parts

How It Works

INHERITANCE MECHANISM
1. DEFINE A BASE CLASS
public class Animal
{
    public virtual void Speak() => Console.WriteLine("...");
}
2. DERIVE A SUBCLASS
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Woof!");
}
3. USE POLYMORPHISM
Animal animal = new Dog();
animal.Speak(); // "Woof!"
COMPOSITION MECHANISM
1. DEFINE THE PARTS
public class Engine
{
    public void Start() => Console.WriteLine("Engine started");
}
2. COMPOSE THEM IN A CONTAINER
public class Car
{
    private Engine _engine = new Engine();
    public void Start() => _engine.Start();
}
3. SWAP OR EXTEND PARTS EASILY

Simple Example

Inheritance — "is-a"

public class Shape
{
    public virtual double Area() => 0;
}
public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override double Area() => Width * Height;
}
public class Circle : Shape
{
    public double Radius { get; set; }
    public override double Area() => Math.PI * Radius * Radius;
}

Meaning: A Rectangle is a Shape. Circle is a Shape. They share a common base and can be used polymorphically.

Composition — "has-a"

public class Logger
{
    public void Log(string message) => Console.WriteLine($"Log: {message}");
}
public class OrderService
{
    private Logger _logger; // composition
    public OrderService(Logger logger) => _logger = logger;
    public void ProcessOrder() => _logger.Log("Order processed");
}

Meaning: OrderService has a Logger. It doesn't inherit from Logger; it uses it. This makes OrderService flexible — you can swap loggers or mock them for testing.

Real-World Example

Consider a notification system that can send emails, SMS, and push notifications. With inheritance, you might create a base Notifier and derive EmailNotifier, SmsNotifier, etc. But if you need to combine behaviors (e.g., email + SMS), inheritance fails.

With composition, you can create small, focused classes and combine them flexibly.

// ─── Parts (composition) ───
public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine($"Email: {message}");
}

public class SmsSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine($"SMS: {message}");
}

public class PushSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine($"Push: {message}");
}

// ─── Composed Notification Service ───
public class NotificationService
{
    private readonly List<IMessageSender> _senders;

    public NotificationService(params IMessageSender[] senders)
    {
        _senders = senders.ToList();
    }

    public void Notify(string message)
    {
        foreach (var sender in _senders)
            sender.Send(message);
    }
}

// ─── Usage ───
var service = new NotificationService(
    new EmailSender(),
    new SmsSender()
);
service.Notify("Order shipped!"); // Sends both email and SMS

Why this is better than inheritance:

Analogy

Building with LEGO

Inheritance is like a pre‑assembled toy — you get a specific shape, and you can't easily change the parts. If you want a different toy, you have to buy a new one (create a new subclass). The structure is fixed.

Composition is like LEGO bricks — you have small, reusable pieces that you can combine in countless ways. You can swap a red brick for a blue one, or add a wheel to a car. The possibilities are endless, and you can change the configuration at any time.

In software, composition gives you the same flexibility: you build complex behavior by combining small, focused objects, rather than locking yourself into a rigid hierarchy.

Under the Hood

INTERNALS
1. INHERITANCE — VTABLE AND MEMORY LAYOUT
2. COMPOSITION — DELEGATION AND OBJECT REFERENCES
3. PERFORMANCE CONSIDERATIONS

Common Confusion

1. "Composition is just using objects" — yes, but it's a design principle

Every class that uses another object is using composition. The principle is about preferring composition over inheritance when designing relationships. It's not just a tactic; it's a mindset to favor loose coupling.

2. "Inheritance is always bad" — no, it has its place

Inheritance is great for genuine "is-a" relationships where the behavior is truly shared and unlikely to change. For example, a List<T> inherits from Collection<T> because a list is a collection. Use inheritance when the relationship is stable and you want to reuse code and enable polymorphism.

3. "Composition is always more code" — yes, but it's worth it

Composition often requires writing more code (delegating methods), but the code is simpler, more testable, and more maintainable. The extra lines are a small price for flexibility.

Common Mistakes

Mistake 1 — Forcing inheritance for convenience

Creating a base class just to share a few helper methods, even when there's no logical "is-a" relationship.

public class UtilityBase
{
    public void Log(string msg) { ... }
}
public class OrderService : UtilityBase { } // Not an "is-a"

Use composition: inject a logger instead.

Mistake 2 — Over-engineering with composition

Composing everything into tiny pieces, making the design overly complex for simple problems.

Use the right tool for the job. For trivial cases, a simple inheritance might be fine.

Mistake 3 — Not using interfaces with composition

Hard-coding concrete components inside a class, making it hard to swap or test.

public class OrderService
{
    private Logger _logger = new Logger(); // tight coupling
}

Depend on an interface: private readonly ILogger _logger; and inject it.

When Should I Use It?

Use inheritance when:

Use composition when:

Rule of thumb: If you're unsure, start with composition. You can always refactor to inheritance later if the relationship becomes clearer. Inheritance is harder to change once it's in place.

Mental Model

Inheritance = "I am a..."
Composition = "I have a..."

Remember:
· Inheritance creates a tight bond — changes to parent affect children.
· Composition creates loose coupling — parts can be swapped independently.
· Favor composition over inheritance, except when the relationship is truly an "is-a" and stable.
· Composition leads to more maintainable and testable code.

Key Takeaway


Check Your Understanding

You've learned the difference between inheritance and composition, and when to use each. Let's see if you can apply these concepts.

1. Which of the following is an example of an "is-a" relationship, where inheritance is appropriate?

Show answer

Correct: B

Why B is correct: A Square is a Shape — this is a classic inheritance relationship. The behavior and properties of a Shape can be shared and overridden by Square.

Why A, C, D are incorrect: All of these are "has-a" relationships. A Car has an Engine, OrderService has a Logger, House has a Door. These are better modeled with composition.

Reinforcement: Inheritance models "is-a"; composition models "has-a".

2. What is a major drawback of using inheritance excessively?

Show answer

Correct: B

Why B is correct: Inheritance couples the subclass to the base class. Changes to the base class can break subclasses unexpectedly (fragile base class problem). This is one of the main reasons to prefer composition.

Why A is incorrect: Inheritance actually promotes code reuse (that's one of its purposes).

Why C is incorrect: Inheritance generally has minimal performance impact; method calls are fast.

Why D is incorrect: Inheritance and interfaces can be used together; they are not mutually exclusive.

Reinforcement: Tight coupling and fragility are the primary drawbacks of overusing inheritance.

3. Which of the following best describes the principle "Favor composition over inheritance"?

Show answer

Correct: B

Why B is correct: The principle is a guideline, not an absolute rule. It suggests that composition often leads to more flexible and maintainable designs, but inheritance still has its place for "is-a" relationships.

Why A is incorrect: It says "favor," not "always." Inheritance is still useful in appropriate scenarios.

Why C is incorrect: Inheritance and interfaces are different concepts; the principle applies to class inheritance.

Why D is incorrect: Composition is a general design principle, not limited to any specific domain.

Reinforcement: Favor composition means prefer it when it makes sense, not that it's always the only choice.

4. You are building a PaymentProcessor that needs to log each transaction. You already have a ILogger interface with multiple implementations (ConsoleLogger, FileLogger). Which approach is better?

Show answer

Correct: C

Why C is correct: PaymentProcessor has a logger (composition). Injecting the logger via the constructor allows you to swap implementations easily, and makes testing simpler (you can mock ILogger). This follows the dependency inversion principle.

Why A and B are incorrect: PaymentProcessor is not a type of logger; inheriting would be a misuse of inheritance (not an "is-a" relationship).

Why D is incorrect: Copy-pasting code is a maintenance nightmare and violates DRY.

Reinforcement: For cross-cutting concerns like logging, composition (dependency injection) is the standard pattern.

5. Which of the following is a benefit of composition over inheritance?

Show answer

Correct: B

Why B is correct: With composition, you can swap out components (e.g., change the engine of a car) at runtime, which is not possible with inheritance (the relationship is fixed at compile time).

Why A is incorrect: Composition does not provide multiple inheritance of classes (C# still has single inheritance), but it does allow combining behavior from many objects, which is a form of multiple inheritance of behavior.

Why C is incorrect: Interfaces are often used in conjunction with composition to define contracts for the parts.

Why D is incorrect: Inheritance method calls can be slightly faster, but the difference is negligible; flexibility is the primary benefit.

Reinforcement: Runtime flexibility is one of the biggest advantages of composition.

You now understand the trade-offs between inheritance and composition — you can design more flexible, maintainable systems!


dotnetmadeeasy.com — Learn C# and .NET, the right way.