Abstract Class vs Interface in C#: Demystifying the Difference
Introduction
Few C# interview questions create as much confusion as:
"What is the difference between an abstract class and an interface,
and when should I use each?"
Many developers memorize a comparison table:
- Abstract class → can have implementation
- Interface → defines a contract
- Abstract class → supports fields
- Interface → does not
- Abstract class → single inheritance
- Interface → multiple inheritance
These statements are useful, but they do not answer the most important
question:
Why would I choose one over the other when designing a real
application?
This article builds the answer from first principles.
The simplest way to remember it is:
An interface describes what a type can do. An abstract class
describes what a family of related types fundamentally is, while
allowing shared behavior.
That distinction is much more valuable than memorizing syntax.
1. Start With the Real Design Problem
Suppose we are building an application that sends notifications.
We may have:
- Email notification
- SMS notification
- Push notification
All of them can send a notification.
That immediately suggests a contract:
public interface INotificationSender
{
Task SendAsync(string message);
}
Now imagine that all notification providers also need:
- Common logging
- Common validation
- A provider name
- Shared retry behavior
That may suggest an abstract base class:
public abstract class NotificationSender
{
protected readonly ILogger Logger;
protected NotificationSender(ILogger logger)
{
Logger = logger;
}
public abstract Task SendAsync(string message);
protected void LogSending(string message)
{
Logger.LogInformation("Sending notification: {Message}", message);
}
}
The two abstractions solve different design problems.
The interface says:
"Anything implementing me promises that it can send a notification."
The abstract class says:
"These objects belong to the same conceptual family and can share
state and implementation."
That is the core distinction.
2. What Is an Interface?
An interface is a contract.
It defines capabilities that a type promises to provide.
public interface IPaymentProcessor
{
Task ProcessPaymentAsync(decimal amount);
}
A class implements that contract:
public class StripePaymentProcessor : IPaymentProcessor
{
public async Task ProcessPaymentAsync(decimal amount)
{
// Stripe-specific implementation
}
}
Another implementation can provide a completely different
implementation:
public class RazorpayPaymentProcessor : IPaymentProcessor
{
public async Task ProcessPaymentAsync(decimal amount)
{
// Razorpay-specific implementation
}
}
The important point is:
The interface does not care how payment is processed.
It only defines the capability.
3. What Is an Abstract Class?
An abstract class is a base type that is incomplete by design.
It can contain:
- Abstract methods
- Concrete methods
- Fields
- Properties
- Constructors
- Protected members
- Shared state
- Static members
Example:
public abstract class PaymentProcessor
{
protected readonly ILogger Logger;
protected PaymentProcessor(ILogger logger)
{
Logger = logger;
}
public abstract Task ProcessPaymentAsync(decimal amount);
protected void LogPayment(decimal amount)
{
Logger.LogInformation(
"Processing payment of {Amount}",
amount);
}
}
A derived class must implement the abstract operation:
public class StripePaymentProcessor : PaymentProcessor
{
public StripePaymentProcessor(ILogger logger)
: base(logger)
{
}
public override async Task ProcessPaymentAsync(decimal amount)
{
LogPayment(amount);
// Stripe-specific implementation
}
}
Here the base class provides common behavior and state, while
subclasses provide specialized behavior.
4. The Most Important Difference
The easiest mental model is:
Interface = Capability
Ask:
"What can this object do?"
Examples:
IEnumerable
IDisposable
IComparable
ILogger
IPaymentProcessor
INotificationSender
A class may support several unrelated capabilities.
For example:
public class OrderService :
IOrderService,
IDisposable,
IAsyncDisposable
{
}
These interfaces describe capabilities/contracts.
Abstract Class = Family / Common Foundation
Ask:
"What is this object fundamentally, and what behavior should all
members of this family share?"
For example:
PaymentProcessor
├── StripePaymentProcessor
├── RazorpayPaymentProcessor
└── PayPalPaymentProcessor
They share a common conceptual foundation.
Therefore an abstract class can make sense.
5. A Powerful Interview Rule
When deciding between them, ask two questions.
Question 1
Is this a capability that unrelated classes may need?
If yes, prefer an interface.
Example:
public interface IAuditable
{
DateTime CreatedAt { get; }
}
A customer, order, invoice, or product could all be auditable.
They do not need to belong to the same inheritance hierarchy.
Question 2
Do these classes belong to the same conceptual family and need
shared state or implementation?
If yes, an abstract class may be appropriate.
Example:
DocumentProcessor
├── PdfProcessor
├── WordProcessor
└── ExcelProcessor
All processors may share:
- File validation
- Logging
- Configuration
- Metrics
- Common processing workflow
An abstract base class can encapsulate that common behavior.
6. Side-by-Side Comparison
| Aspect | Difference |
|---|---|
| Purpose | Abstract class provides a common foundation. Interface defines a contract or capability. |
| Implementation | Abstract class can provide shared implementation. Interface can also provide default implementations in modern C#. |
| State | Abstract class can contain instance state. Interface cannot contain instance fields. |
| Constructors | Abstract class can have constructors. Interfaces cannot have instance constructors. |
| Inheritance | A class can inherit only one base class. A class can implement multiple interfaces. |
| Coupling | Abstract classes create a stronger relationship. Interfaces generally provide looser coupling. |
| Best use | Use for shared behavior, state, or workflow. |
| Typical example |
FileProcessor → PdfProcessor, ExcelProcessor
|
| Interface example |
INotificationSender → EmailSender, SmsSender
|
7. Why Does C# Allow Multiple Interfaces but Only One Base Class?
Consider:
public class ReportService :
IReportGenerator,
IAuditable,
IDisposable
{
}
A class can implement multiple interfaces.
Why?
Because these interfaces represent independent capabilities:
ReportService
├── IReportGenerator
├── IAuditable
└── IDisposable
But C# allows only one class inheritance chain:
public class ReportService : BaseReportService
{
}
The reason is that inheriting implementation and state creates much
stronger relationships.
If multiple base classes were allowed, problems such as conflicting
implementations and state become much harder to reason about.
So C# deliberately separates:
Capability composition
from
Implementation inheritance
8. Real Example: Interface Is Better
Imagine a logging system.
public interface INotificationSender
{
Task SendAsync(string recipient, string message);
}
Implementations:
public class EmailNotificationSender : INotificationSender
{
public Task SendAsync(string recipient, string message)
{
// Send email
return Task.CompletedTask;
}
}
public class SmsNotificationSender : INotificationSender
{
public Task SendAsync(string recipient, string message)
{
// Send SMS
return Task.CompletedTask;
}
}
Why interface?
Because email and SMS are not necessarily related by inheritance.
They simply share a capability:
They can send notifications.
The interface keeps the design loosely coupled.
9. Real Example: Abstract Class Is Better
Consider file processors.
public abstract class FileProcessor
{
public async Task ProcessAsync(string filePath)
{
Validate(filePath);
await ReadAsync(filePath);
Log(filePath);
}
protected void Validate(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("Invalid file path.");
}
protected abstract Task ReadAsync(string filePath);
protected void Log(string filePath)
{
Console.WriteLine($"Processed {filePath}");
}
}
Now:
public class PdfProcessor : FileProcessor
{
protected override Task ReadAsync(string filePath)
{
// PDF-specific logic
return Task.CompletedTask;
}
}
And:
public class ExcelProcessor : FileProcessor
{
protected override Task ReadAsync(string filePath)
{
// Excel-specific logic
return Task.CompletedTask;
}
}
The abstract class provides a common workflow:
Validate
↓
Read
↓
Log
Only the specialized operation changes.
This is a classic use case for an abstract class.
10. Interface Does NOT Mean "No Implementation"
This is an important misconception.
Modern C# interfaces can contain default implementations.
For example:
public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log($"ERROR: {message}");
}
}
So this statement is outdated:
"Interfaces cannot contain implementation."
A better statement is:
Interfaces primarily define contracts, but modern C# allows them to
provide default implementations as well.
However, that does not mean interfaces should become replacements for
abstract classes.
Default interface implementations solve specific versioning and
API-evolution scenarios. They do not provide instance state or turn an
interface into a normal stateful base class.
11. Abstract Class vs Interface in Dependency Injection
This is where interfaces are extremely common in .NET applications.
Example:
public interface IOrderRepository
{
Task GetByIdAsync(int id);
}
Implementation:
public class SqlOrderRepository : IOrderRepository
{
public Task GetByIdAsync(int id)
{
// Database implementation
throw new NotImplementedException();
}
}
Register it:
builder.Services.AddScoped();
The application depends on:
IOrderRepository
instead of:
SqlOrderRepository
This provides loose coupling.
It also makes testing easier:
public class FakeOrderRepository : IOrderRepository
{
// Test implementation
}
This is one reason interfaces are heavily used in enterprise .NET
applications.
12. Does That Mean We Should Always Use Interfaces?
No.
This is another common mistake.
Some developers create an interface for every single class:
CustomerService
ICustomerService
OrderService
IOrderService
ProductService
IProductService
without any real architectural reason.
This can produce unnecessary abstraction.
For example, if a class:
- Has one implementation
- Is internal to a small module
- Does not need polymorphism
- Does not represent a meaningful boundary
then creating an interface may add complexity without providing much
value.
The goal is not:
"Use interfaces everywhere."
The goal is:
Use abstraction where it creates a useful boundary.
13. When Should You Use an Interface?
Prefer an interface when:
1. You need a contract
public interface IPaymentProcessor
{
Task ProcessAsync(decimal amount);
}
2. Multiple unrelated classes need the same capability
Order
Customer
Invoice
Product
could all implement:
IAuditable
3. You need multiple capabilities
public class Order :
IAuditable,
IValidatable,
IExportable
{
}
4. You want loose coupling
Especially across application boundaries such as:
Controller
↓
Application Service
↓
Interface
↓
Implementation
5. You expect multiple implementations
For example:
IPaymentProcessor
├── StripePaymentProcessor
├── RazorpayPaymentProcessor
└── MockPaymentProcessor
14. When Should You Use an Abstract Class?
Prefer an abstract class when:
1. There is a strong "is-a" relationship
PdfProcessor is a FileProcessor
ExcelProcessor is a FileProcessor
2. Subclasses need shared implementation
protected void Validate()
{
}
3. Subclasses need shared state
protected readonly ILogger Logger;
4. You want to enforce a common workflow
For example:
Validate
↓
Authenticate
↓
Process
↓
Audit
This is commonly implemented using the Template Method pattern.
5. You control the inheritance hierarchy
An abstract base class creates a stronger relationship than an
interface, so it should be used deliberately.
15. A Real Enterprise Example
Imagine an order-processing platform.
We might have:
IOrderProcessor
as the contract:
public interface IOrderProcessor
{
Task ProcessAsync(Order order);
}
Different implementations:
OnlineOrderProcessor
StoreOrderProcessor
PartnerOrderProcessor
Now suppose every processor needs common behavior:
Validate order
Create correlation ID
Start telemetry
Execute processing
Audit result
An abstract class could provide that workflow:
public abstract class OrderProcessorBase
{
public async Task ProcessAsync(Order order)
{
Validate(order);
await ProcessCoreAsync(order);
Audit(order);
}
protected virtual void Validate(Order order)
{
// Common validation
}
protected abstract Task ProcessCoreAsync(Order order);
protected virtual void Audit(Order order)
{
// Common audit
}
}
A concrete processor then implements only the variable portion:
public class OnlineOrderProcessor : OrderProcessorBase
{
protected override Task ProcessCoreAsync(Order order)
{
// Online-order-specific logic
return Task.CompletedTask;
}
}
This gives us:
Common workflow
↓
Abstract base class
↓
Specialized implementations
If consumers should depend on a stable contract, we can additionally
expose an interface:
IOrderProcessor
↑
OrderProcessorBase
↑
OnlineOrderProcessor
The two mechanisms are not competitors.
They can work together.
16. Interface + Abstract Class Together
This is a powerful enterprise pattern.
public interface IFileProcessor
{
Task ProcessAsync(string filePath);
}
Then:
public abstract class FileProcessorBase : IFileProcessor
{
public async Task ProcessAsync(string filePath)
{
Validate(filePath);
await ProcessCoreAsync(filePath);
Audit(filePath);
}
protected abstract Task ProcessCoreAsync(string filePath);
protected virtual void Validate(string filePath)
{
// Common validation
}
protected virtual void Audit(string filePath)
{
// Common auditing
}
}
Concrete implementation:
public class PdfProcessor : FileProcessorBase
{
protected override Task ProcessCoreAsync(string filePath)
{
// PDF processing
return Task.CompletedTask;
}
}
Now we have:
Interface
Defines the external contract
Abstract class
Provides reusable implementation
Concrete class
Provides specific behavior
This is often cleaner than trying to force one abstraction to do
everything.
17. The "Can Do" vs "Is A" Rule
A classic rule is:
Interface = can do
Abstract class = is a
For example:
Bird
A bird:
is a Bird
But:
IFlyable
means:
can fly
So:
public abstract class Bird
{
public abstract void Eat();
}
and:
public interface IFlyable
{
void Fly();
}
Then:
public class Eagle : Bird, IFlyable
{
public override void Eat()
{
}
public void Fly()
{
}
}
But be careful.
The "is-a / can-do" rule is a useful mental model, not an absolute law.
Modern software design often focuses more on:
- Responsibilities
- Coupling
- Composition
- Polymorphism
- Reuse
- Changeability
than on inheritance terminology alone.
18. Composition vs Inheritance
One of the most important design l
Top comments (0)