Dependency Inversion Principle
The Dependency Inversion Principle (DIP) says high-level modules should not depend on low-level modules; both depend on abstractions.
Introduction
The Dependency Inversion Principle (DIP) says high-level modules should not depend on low-level modules; both depend on abstractions. OrderService depends on PaymentGateway interface, not StripeClient concrete class.
DIP is how LLD designs stay testable: inject in-memory fakes in unit tests, swap Redis for map in interviews. Constructor injection in Java is the standard pattern interviewers expect.
Combine DIP with factories when construction logic is non-trivial.
Understanding the topic
Key concepts
- High-level policy independent of low-level details.
- Abstractions owned by domain layer, not infrastructure.
- Dependency injection: constructor > setter > field.
- Inversion of Control containers in Spring; manual wiring in LLD.
- DIP enables mocking external systems in tests.
- Do not invert everything — stable utilities may be direct deps.
flowchart LRSRP --> OCP --> LSP --> ISP --> DIP
Step-by-step explanation
- Identify high-level orchestrator (ATM, BookingService).
- Extract interfaces for IO, persistence, payment, clock.
- Implement concretes in infrastructure package.
- Wire via constructor at composition root.
- Use factory when choosing implementation at runtime.
- Keep domain free of JDBC, HTTP, file path strings.
Informative example
ATM depends on abstractions for hardware and persistence:
public interface CashDispenser {boolean dispense(Money amount);}public interface TransactionStore {void save(Transaction txn);}public final class AtmService {private final CashDispenser dispenser;private final TransactionStore store;public AtmService(CashDispenser dispenser, TransactionStore store) {this.dispenser = dispenser;this.store = store;}public Result withdraw(Account account, Money amount) {if (!account.debit(amount)) return Result.failed("insufficient funds");if (!dispenser.dispense(amount)) {account.credit(amount);return Result.failed("dispenser error");}store.save(Transaction.withdraw(account.id(), amount));return Result.ok();}}
AtmService knows nothing about USB hardware or SQL — interview tests pass with fakes.
Real-world use
Real-world applications
- Every LLD service orchestrating external systems.
- Unit testing without databases or network.
- Swapping implementations per environment.
Best practices
- Constructor inject required dependencies as final fields.
- Program to interfaces in field types.
- Composition root wires concrete graph once.
- Avoid service locator anti-pattern in domain code.
- Keep interfaces in domain package; impls in infra.
Common mistakes
- new StripeClient() inside business method.
- Interfaces owned by infrastructure leaking upward.
- Injecting concrete List when abstraction unnecessary.
- Circular dependencies between high-level modules.
Advanced interview questions
Q1BeginnerWhat is DIP?
Q2BeginnerHow is DIP different from DI?
Q3IntermediateWhy constructor injection?
Q4IntermediateWhen can you depend on a concrete class?
Q5AdvancedDesign DIP for movie booking with external payment.
Summary
High-level code depends on abstractions it owns. Constructor injection is the Java LLD default. DIP enables tests with fakes and mocks. Infrastructure implements domain interfaces. SOLID together produces maintainable object graphs.