Abstraction
Abstraction means showing only what matters and hiding the how. You drive a car with a steering wheel and pedals — not by controlling fuel injection yourself.
On this page · 6 min · Beginner
On this page
- In simple words
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Beginner
- Updated
- Oct 27, 2026
Related tutorials
In simple words#
A TV remote shows simple buttons; the electronics inside are hidden. Abstraction gives users a simple model and hides the complex parts.
- Abstraction
- Focus on what, hide how.
- Abstract class
- Partial blueprint with some missing pieces.
- Interface
- Pure contract with no implementation.
Learning objectives#
- Explain abstraction and how it differs from encapsulation
- Define contracts with interfaces
- Share partial implementation with abstract classes
- Decide between an abstract class and an interface
Explanation#
Abstraction focuses on what an object does; encapsulation focuses on hiding how it stores its data. They work together.
An interface is a pure contract: a list of members a class promises to provide, with no state. A class can implement many interfaces.
An abstract class cannot be instantiated. It can contain both implemented methods (shared code) and abstract methods (which subclasses must implement). A class can inherit from only one class.
Code that depends on an abstraction (IPaymentGateway) instead of a concrete class (RazorpayGateway) is easier to test and to change.
Code example: Interface plus abstract base class#
public interface INotifier
{
Task SendAsync(string to, string message);
}
// Abstract class: shared logic + a step subclasses must provide
public abstract class NotifierBase : INotifier
{
public async Task SendAsync(string to, string message)
{
if (string.IsNullOrWhiteSpace(to)) throw new ArgumentException("Recipient required");
await DeliverAsync(to, message.Trim());
Console.WriteLine(quot;[{GetType().Name}] sent to {to}");
}
protected abstract Task DeliverAsync(string to, string message);
}
public class EmailNotifier : NotifierBase
{
protected override Task DeliverAsync(string to, string message) =>
Task.CompletedTask; // call an SMTP service here
}
public class SmsNotifier : NotifierBase
{
protected override Task DeliverAsync(string to, string message) =>
Task.CompletedTask; // call an SMS API here
}
// The caller depends only on the abstraction.
public class OrderService(INotifier notifier)
{
public Task ConfirmAsync(string customer) =>
notifier.SendAsync(customer, "Your order is confirmed.");
}
// new NotifierBase(); // ✗ compile error: cannot create an instance of an abstract class
[EmailNotifier] sent to asha@example.com
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- INotifier is the contract: anything that can SendAsync.
- NotifierBase implements the shared validation and logging once, then delegates the actual delivery to an abstract method.
- EmailNotifier and SmsNotifier only fill in DeliverAsync — no duplicated validation.
- OrderService receives an INotifier. It works with email, SMS, or a fake notifier in tests, without changing a line.
- Trying to create a NotifierBase directly fails at compile time because it is abstract.
Real-world use case#
ASP.NET Core is built on abstractions: ILogger, IConfiguration, IDistributedCache. You register a concrete implementation in Program.cs and the rest of the app depends only on the interface.
Common mistakes#
An interface with exactly one implementation that will never change adds indirection without benefit. Introduce abstractions when you need flexibility or testability.
An IStorage interface with a method like ExecuteSqlQuery leaks the implementation. Name members after what callers need, not how it is done.
Best practices#
- Depend on interfaces at boundaries (databases, email, payments, clocks)
- Use abstract classes to share code between closely related types
- Keep interfaces small and focused
- Name interfaces after capabilities (INotifier, IRepository<T>)
Remember this#
Abstraction = show WHAT it does, hide HOW.
Abstract class = shared code. Interface = pure contract.
Depend on the abstraction, not the details.
Practice exercise#
Your task: Write a TypeScript interface Shape with area(): number, and two classes Circle and Rectangle that implement it. Write a function totalArea(shapes: Shape[]).
Need a hint?
Show solution
interface Shape { area(): number }
class Circle implements Shape {
constructor(private r: number) {}
area() { return Math.PI * this.r ** 2; }
}
class Rectangle implements Shape {
constructor(private w: number, private h: number) {}
area() { return this.w * this.h; }
}
const totalArea = (shapes: Shape[]) => shapes.reduce((sum, s) => sum + s.area(), 0);
Mini quiz#
Which can contain implemented methods AND cannot be instantiated?
Want more? Practise all OOP Concepts MCQs · OOP Concepts interview questions
My notes#
Saved in this browser.