OOP Concepts · The Four Pillars

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.

Beginner 6 min Lesson 10 of 38Examples use modern JavaScript (ES2022 private fields) and C# 13.
Before you start:EncapsulationPolymorphism
0/38 in OOP Concepts
On this page · 6 min · Beginner

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#

C#Notifications.cs Illustrative
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
Expected output
[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#

  1. INotifier is the contract: anything that can SendAsync.
  2. NotifierBase implements the shared validation and logging once, then delegates the actual delivery to an abstract method.
  3. EmailNotifier and SmsNotifier only fill in DeliverAsync — no duplicated validation.
  4. OrderService receives an INotifier. It works with email, SMS, or a fake notifier in tests, without changing a line.
  5. 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#

Abstracting too early

An interface with exactly one implementation that will never change adds indirection without benefit. Introduce abstractions when you need flexibility or testability.

Leaky abstractions

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?
Use class Circle implements Shape, and sum the areas with reduce.
Show solution
TypeScript Illustrative
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#

Abstraction quizMultiple choice Question 1 of 3

Which can contain implemented methods AND cannot be instantiated?

Score so far: 0/0

Want more? Practise all OOP Concepts MCQs · OOP Concepts interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.