Overview
Abstraction means exposing what something does while hiding how it does it. A caller of PaymentGateway.charge() does not need to know whether it talks to Stripe or a bank, retries, or logs. Good abstractions model the essential behavior and leave out incidental details.
In code, abstraction appears as interfaces, abstract classes, and well-named methods. Encapsulation hides data; abstraction hides complexity and implementation choices. Choosing the right level of abstraction, not too generic and not too leaky, is one of the core skills in LLD.
You press 'volume up' without knowing about infrared codes or amplifier circuits. Any brand of TV can respond to the same buttons.
When to use it
- Several implementations of the same capability (storage, notification, payment).
- Callers should not depend on a vendor or technology.
- Complex subsystems that need a simple entry point.
Where it shows up in interviews
Recognize it when: support SMS, email, and push; or S3 and local disk.
- Design a notification service
- Design a file storage library
Recognize it when: callers repeat many low-level steps.
- Design a logging framework
- Design an ATM
Where it is used in real software
Java code talks to java.sql.Connection; drivers for PostgreSQL or MySQL implement it.
Spring Resource or Node's streams let the same code read from disk, classpath, or network.
Files abstract away disks, SSDs, and network storage behind open/read/write.
Key terms
- Interface
- A contract of operations without implementation.
- Abstract class
- Partial implementation that subclasses complete.
- Leaky abstraction
- Implementation details that escape to callers.
- Level of abstraction
- How much detail a method or type exposes.
How it works, step by step
- 1Describe the capability in domain terms
send(notification), not postToTwilio(json).
- 2Define the minimal contract
Only operations every implementation supports.
- 3Hide vendor types
Return your own result types, not SDK objects.
- 4Implement behind the contract
EmailNotifier, SmsNotifier.
- 5Keep each method at one level
High-level methods call other high-level methods.
Concrete vs abstract dependency
An order service needs to send confirmations
| Change | Depends on TwilioClient | Depends on Notifier interface |
|---|---|---|
| Switch to another SMS vendor | Edit OrderService | Add a new Notifier implementation |
| Add email confirmations | More vendor code in OrderService | Composite or second Notifier |
| Unit test OrderService | Mock Twilio SDK internals | Pass a fake Notifier |
NOWChange: Switch to another SMS vendor | Depends on TwilioClient: Edit OrderService | Depends on Notifier interface: Add a new Notifier implementation
Depending on an abstraction isolates the service from vendor changes and simplifies tests.
Implementation
interface Notifier { send(to: string, message: string): Promise<{ delivered: boolean }>;} class SmsNotifier implements Notifier { constructor(private client: { messages: { create(o: object): Promise<{ status: string }> } }) {} async send(to: string, message: string) { const res = await this.client.messages.create({ to, body: message }); return { delivered: res.status !== "failed" }; // vendor type does not leak }} class OrderService { constructor(private notifier: Notifier) {} async confirm(orderId: string, phone: string) { await this.notifier.send(phone, `Order ${orderId} confirmed`); }}Complexity and performance
Negligible.
Only where variation exists.
Trade-offs
Abstractions ease change but add types to navigate; do not abstract things that will never vary.
A generic interface may hide useful features of specific implementations.
Variants and related techniques
Pass functions instead of interfaces for single-method behaviors.
A simple API over a complex subsystem.
Common mistakes
- Premature abstraction.
Fix: Wait for a second real implementation or a clear testing need.
- Leaking implementation types.
Fix: Map vendor exceptions and DTOs to your own types.
- Mixing levels in one method.
Fix: Split low-level details into helpers.
Interview questions
Abstraction vs encapsulation?
Encapsulation hides an object's data and protects its invariants; abstraction hides implementation complexity behind a simpler contract. They often work together.
What is a leaky abstraction?
One where callers must understand the hidden details to use it correctly, such as having to catch vendor-specific exceptions or know about retry behavior.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Abstract a FileStorage over disk and S3 | Easy | Contract design. |
| Design a PaymentGateway for 3 providers | Medium | Mapping results and errors. |