Skip to content
LLD Learn/SOLID Principles
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Dependency Inversion Principle

6 min read

You'll learn to

  • -Depend on abstractions instead of concrete implementations, and explain why that makes a design testable and swappable
  • -Distinguish dependency inversion from dependency injection, since interviewers often expect both terms handled correctly

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules - both should depend on abstractions. In an order-processing system, OrderService (high-level policy) should not directly instantiate and call MySQLOrderRepository (a low-level detail); both should depend on an OrderRepository interface instead.

Why "Inversion"?

Without DIP, the natural dependency direction is high-level code depending directly on low-level details: OrderService new()s up a MySQLOrderRepository and calls it directly, which means OrderService cannot be tested without a real database and cannot be reused with a different storage backend without editing OrderService itself. DIP inverts that direction: the low-level MySQLOrderRepository now depends on (implements) the OrderRepository interface that the high-level OrderService defines the shape of. The dependency arrow that used to point from high-level to low-level now points from low-level to an abstraction that high-level code owns.

Inverting the dependency direction
# Before: OrderService is tightly coupled to a concrete, low-level detail
class OrderService:
    def __init__(self):
        self._repo = MySQLOrderRepository()  # concrete dependency baked in

# After: both depend on an abstraction; OrderService owns the contract shape
class OrderRepository(ABC):
    @abstractmethod
    def save(self, order: Order) -> None: ...

class MySQLOrderRepository(OrderRepository):
    def save(self, order: Order) -> None: ...  # low-level detail

class InMemoryOrderRepository(OrderRepository):
    def save(self, order: Order) -> None: ...  # trivial to swap in for tests

class OrderService:
    def __init__(self, repo: OrderRepository):  # depends on the abstraction
        self._repo = repo

Dependency Inversion vs. Dependency Injection

These two terms are related but distinct, and interviewers notice when they get conflated. Dependency Inversion is the design principle: depend on abstractions, not concrete details. Dependency Injection is one common technique for satisfying that principle: passing a dependency into a class (via constructor, method, or setter) rather than having the class construct it internally, as shown in the OrderService(repo) example above. You can technically do dependency injection without following dependency inversion (injecting a concrete MySQLOrderRepository instead of an OrderRepository interface) - it is the combination of both that unlocks real testability and swappability.

DIP is the payoff for every earlier "design to the interface" habit in this course - it is the formal name for exactly that instinct, applied specifically to how high-level and low-level modules relate to each other.

Interview Signal

How would you explain why OrderService directly instantiating MySQLOrderRepository inside its constructor is a problem?

Weak Answer

"It's fine as long as MySQLOrderRepository works correctly."

Strong Answer

"It couples a high-level policy class to a low-level storage detail. OrderService can't be unit tested without a real database, and swapping storage backends means editing OrderService itself. I'd have OrderService depend on an OrderRepository interface and receive an implementation via constructor injection - that's dependency inversion plus dependency injection working together."

Check Yourself1 / 3

What does the Dependency Inversion Principle actually invert?

ScaleDojo Logo
Initializing ScaleDojo