← Back to Roadmap

Dependency Inversion Principle (DIP)

Hard

In 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 implementation

Why 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: