Dependency Inversion Principle (DIP)
HardIn plain terms
The Dependency Inversion Principle has two parts: (1) High-level modules should not depend on low-level modules; both should depend on abstractions. (2) Abstractions should not depend on details; details should depend on abstractions.
In practice, your business logic should depend on an interface (e.g. IEmailService), not a concrete class (SmtpEmailService). You then inject the concrete implementation (via constructor or setter). That way you can swap implementations, mock for tests, and change low-level details without touching high-level code. DIP is the basis for dependency injection and inversion of control.
What you need to know
- •Depend on abstractions
- •Inject dependencies
- •Enables testing and swapping implementations
Example
Code is language-agnostic in spirit; adapt the idea to your language:
// High-level depends on abstraction
class OrderService {
constructor(emailService) { // interface, not concrete
this.emailService = emailService;
}
placeOrder(order) {
// ...
this.emailService.send(order.email, "Confirmed");
}
}
// Inject real or mock implementationWhy this matters
DIP and dependency injection are standard in senior interviews. You may be asked how you would unit test a class or make it flexible for different implementations.
How it connects
High-level modules depend on abstractions (interfaces); low-level modules implement them. Injection (constructor/setter) makes this explicit. Enables testing (mock implementations) and OCP (swap implementations without changing high-level code).
Interview focus
Be ready to explain these; they come up often.
- ▸Depend on abstractions (interfaces), not concrete classes. Inject dependencies.
- ▸Benefit: testability (inject mocks), flexibility (swap implementations).
- ▸Example: OrderService receives IEmailService in constructor, not new SmtpService().
Learn more
Dive deeper with these resources: