Dependency Inversion Principle
High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details — details depend on abstractions.
Depend on abstractions.
The Dependency Inversion Principle (DIP) says your important business logic (high-level) shouldn't hard-wire itself to specific tools (low-level) like a particular database or email library. Both should talk through an abstraction (interface).
Example: instead of OrderService creating a MySQLDatabase directly, it
depends on a Repository interface. You can swap MySQL for Postgres or an in-memory fake
without touching OrderService.
# Problem: high-level policy depends on a low-level detail (SMTP). class PasswordReset: def send(self, email, token): smtp = SmtpClient("smtp.prod:25") # concrete dependency smtp.send(email, f"reset={token}") # Can't test without a real mail server; swapping providers hurts.
# Fix: both depend on an abstraction; inject the concrete at the edge. from abc import ABC, abstractmethod class Mailer(ABC): @abstractmethod def send(self, email, body): ... class SmtpMailer(Mailer): def send(self, email, body): SmtpClient("smtp.prod:25").send(email, body) class PasswordReset: def __init__(self, mailer: Mailer): self.mailer = mailer # depend on Mailer, not SmtpClient def send(self, email, token): self.mailer.send(email, f"reset={token}") # Production: PasswordReset(SmtpMailer()) # Tests: PasswordReset(FakeMailer())
Invert the dependency direction.
Normally high-level code depends on low-level code. DIP inverts this: the high-level module owns an abstraction, and the low-level module implements it. The arrow now points from detail → abstraction, so policy no longer depends on mechanism.
Ownership, boundaries, and clean architecture.
- Who owns the abstraction — the high-level/client side; the interface expresses its needs, so it's stable.
- Architectural boundaries — DIP lets source-code dependencies cross boundaries opposite to the flow of control (the basis of clean/hexagonal architecture).
- Plugins — low-level details become plugins to high-level policy.
- Composition root — concrete wiring happens in one place at startup.
Interview Questions
High-level modules should not depend on low-level modules; both should depend on abstractions. And abstractions should not depend on details — details should depend on abstractions.
High-level modules contain the important business policy/logic (what the app does). Low-level modules are the details/mechanisms (database access, file I/O, network, third-party libraries) that carry out that policy.
Instead of NotificationService instantiating EmailSender directly, define a MessageSender interface. NotificationService depends on the interface, and EmailSender (or SmsSender) implements it, injected from outside.
Decoupling: high-level logic no longer depends on concrete details, so you can swap implementations (e.g. change database, mock for tests) without changing the business code, making the system flexible, testable, and maintainable.
An interface or abstract class that defines what operations are available without specifying how they're done — e.g. a Repository or Logger interface that concrete classes implement.
Because code depends on abstractions, you can inject a fake/mock implementation in tests instead of the real database, network, or service — letting you test business logic in isolation, quickly and deterministically.
The direction of the source-code dependency. Conventionally, high-level code calls and therefore depends on low-level code (policy → detail). DIP inverts that compile-time dependency: an abstraction sits between them, owned by the high-level side, and the low-level module depends on (implements) that abstraction. So at the source level the arrow now points from the detail toward the abstraction, opposite to the traditional layered dependency — even though at runtime the high-level code still calls into the low-level implementation.
DIP is the design principle (depend on abstractions, invert the dependency). Dependency Injection (DI) is a technique for providing a class's dependencies from the outside (via constructor, setter, or parameters) rather than the class creating them. Inversion of Control (IoC) is the broader idea of handing control of flow/creation to a framework or external mechanism (of which DI is one form). You can follow DIP using DI, and a DI/IoC container can automate the wiring, but DI without abstractions doesn't achieve DIP, and DIP is about what you depend on, while DI is about how the dependency is supplied.
Not by itself. If you inject a concrete class (e.g. constructor-inject MySQLDatabase directly), you've done DI but not DIP — the high-level module still depends on a low-level detail. DIP requires depending on an abstraction (interface) and injecting an implementation of it. DI is the delivery mechanism; DIP is satisfied only when what's depended upon is an abstraction owned by/serving the high-level policy.
Common forms: constructor injection (dependencies passed to the constructor — preferred, guarantees a fully-initialized object and immutable references); setter/property injection (set after construction — useful for optional dependencies but allows partially-configured objects); and method/parameter injection (passed to the specific method that needs it). Frameworks may also do interface or field injection. Constructor injection is generally favored for required dependencies because it makes them explicit and enforces valid state.
DIP is a primary mechanism for achieving OCP. By depending on abstractions, high-level modules are closed for modification yet open for extension: you add new implementations of the abstraction (new low-level detail) without changing the high-level code. The stable interface is the extension point; new plugins extend behavior. In short, inverting dependencies onto abstractions is what lets you extend a system by adding implementations rather than editing existing policy code.
The high-level (client) side. The interface should express the needs of the high-level policy, be phrased in its terms, and live in (or near) its module — so the abstraction is stable and the low-level detail conforms to it. This is what makes the dependency truly inverted: the detail depends on the client's abstraction, not the client on the detail. If the low-level module defines the interface reflecting its own capabilities, you haven't really inverted anything.
Clean/hexagonal/onion architectures put business logic at the center and infrastructure at the edges, with a rule that source dependencies point inward. DIP is the tool that makes this possible: the domain defines "ports" (abstractions) for what it needs (repositories, gateways), and outer "adapters" (DB, web, messaging) implement those ports. So even though control flows inward (a web request drives a use case) and then outward (the use case saves to a DB), the compile-time dependency of the adapter points inward to the domain's interface. This keeps the core independent of frameworks and detail, testable in isolation, and lets infrastructure be swapped as plugins — all direct consequences of inverting dependencies onto domain-owned abstractions.
A composition root is the single place (typically at application startup, e.g. main or a DI container configuration) where concrete implementations are instantiated and wired to the abstractions that need them. DIP pushes all concrete "newing up" of dependencies out of the business code (which only sees interfaces), so something must decide which implementations to use — the composition root does this once, at the outermost layer. Centralizing wiring there keeps the rest of the code depending only on abstractions, makes the object graph explicit, eases swapping implementations (prod vs test), and prevents the Service Locator anti-pattern of scattering dependency lookups throughout the codebase.
Indiscriminate DIP creates an interface for every class (often 1:1 "header interfaces" with a single implementation), adding indirection that obscures navigation and control flow, more files/boilerplate, and cognitive overhead — abstractions that don't actually abstract anything. Over-reliance on DI containers can make the object graph implicit and errors surface at runtime rather than compile time. The pragmatic guidance: invert dependencies at meaningful boundaries where volatility, testing needs, or multiple implementations justify it (external systems, things you mock, likely-to-change details), and depend on concretes for stable, internal collaborators. DIP buys decoupling at the cost of indirection; apply it where the decoupling has real value, not reflexively.
Martin notes you don't need to invert dependencies on stable, non-volatile things (e.g. String, standard collections, mature OS APIs) — depending on them directly is fine because they rarely change. DIP targets volatile concretes: modules under active development, things with multiple or swappable implementations, and external systems (DBs, APIs) likely to change. The principle is really "don't depend on volatile concrete details; depend on abstractions instead." This keeps you from over-abstracting ubiquitous stable types while protecting your policy from the parts of the system genuinely prone to change, focusing the cost of abstraction where it pays off.
Both provide dependencies without a class newing them up, but differ in visibility and coupling. DI passes dependencies in (e.g. via constructor), so a class's requirements are explicit in its signature, it stays unaware of any container, and it's easy to test by passing fakes. Service Locator has the class ask a global/registry (locator.get(Foo.class)) for its dependencies, hiding them inside the implementation. Criticisms of Service Locator: dependencies become implicit (you can't tell what a class needs without reading its body), it couples code to the locator, and it can defer failures to runtime. Both can satisfy DIP (you still depend on abstractions), but DI is generally preferred because it makes dependencies explicit and keeps classes ignorant of the resolution mechanism, whereas Service Locator is often considered an anti-pattern in modern design.
They reinforce each other. DIP says depend on abstractions; ISP says those abstractions should be narrow and client-specific (so a high-level module depends only on the exact operations it needs, not a fat contract); LSP ensures the concrete implementations plugged in behind the abstraction are truly substitutable (so swapping details can't break the policy relying on the interface's contract). Together they define healthy architectural boundaries: DIP sets the direction of dependency (toward abstractions), ISP shapes the abstraction (small, role-based ports), and LSP guarantees the interchangeability the whole scheme assumes. A boundary that inverts dependencies onto minimal, well-behaved interfaces is exactly what clean architecture's ports/adapters embody, and violating any of the three (fat ports, non-substitutable adapters, or depending on concretes) degrades the decoupling.
The "D" — Dependency Inversion Principle, the last of the five SOLID principles.
High-level business code that directly instantiates concrete low-level classes (e.g. new MySqlConnection(), new SmtpClient()) or imports a specific framework/driver, tying policy to a particular detail.
Yes. If business logic depends on a Repository/Gateway abstraction, you can provide a different implementation (Postgres instead of MySQL, a new payment provider) without touching the business code — only the wiring at the composition root changes.
Supplying a class's dependencies through its constructor parameters (usually as interfaces), rather than the class creating them internally. It makes dependencies explicit and required, and is the most common way to satisfy DIP.
In naive layering, the UI depends on business logic which depends on data access — policy depends on detail. That means changes to volatile low-level details (DB, frameworks) ripple up into stable high-level policy, and the important business rules can't be compiled, tested, or reused without dragging the infrastructure along. DIP inserts abstractions so the high-level policy depends only on interfaces it defines, and the low-level layers depend inward on those interfaces — protecting the valuable, stable core from the churn of the details.
Absolutely. DIP is about depending on abstractions; you satisfy it with plain manual (or "pure") DI — construct concrete implementations at the top of the app and pass them into the objects that need them via constructors. A DI/IoC container merely automates that wiring for large graphs; it's a convenience, not a requirement. Many argue pure DI is preferable for small/medium apps because the object graph is explicit and errors are compile-time rather than hidden in container configuration.
You get the testability/wiring benefits of DI but not the decoupling of DIP: the high-level module still references a specific concrete type, so you can't substitute alternative implementations, and changes to that concrete class still ripple into the policy. You also can't easily mock it unless the class is designed for it. True DIP requires the dependency to be an abstraction, so the concrete implementation is interchangeable and the policy is insulated from it.
"Ports" are the abstractions (interfaces) the application core defines for what it needs or offers; "adapters" are the concrete implementations for specific technologies (a Postgres adapter, an HTTP adapter). DIP is what makes the port belong to the core and the adapter depend on it, so infrastructure plugs into the application rather than the application depending on infrastructure. Hexagonal architecture is essentially DIP applied systematically at the boundary between domain and the outside world.
Creating an interface for every single class ("header interfaces" with one implementation) adds indirection, files, and cognitive load while abstracting nothing real — you jump through an interface to reach the only implementation, harming readability and navigation. Overusing containers can also make the object graph implicit and push errors to runtime. Avoid it by inverting dependencies only at meaningful seams: volatile details, external systems, things you actually need to mock or swap, and true architectural boundaries. Depend directly on stable, internal collaborators that have no realistic alternative implementation. The heuristic: introduce an abstraction when there's genuine variability or a testing/boundary need, not reflexively.
At runtime, control flows from high-level policy into low-level detail (a use case calls the database to save). Without DIP, the source/compile-time dependency points the same way (policy imports the DB class). DIP inserts an interface owned by the high-level module: the use case depends on Repository, and the DB adapter implements Repository, so the adapter's source depends on the policy's abstraction. Now the compile-time arrow points from detail → policy, opposite to the runtime call direction. This decoupling is the mechanism that lets clean architecture keep all source dependencies pointing inward toward the stable domain even though execution flows outward to infrastructure — the "inversion" is precisely this reversal of the source dependency relative to control flow.
With the high-level/consumer, not with the low-level implementation. If the interface is packaged in the same module as (or defined by) the concrete detail, the consumer still transitively depends on that low-level package — no real inversion. Placing the abstraction in the high-level module (or a neutral package the high-level owns) means the detail's package depends on the policy's package, achieving the dependency inversion at the component level. Robert Martin stresses this: the boundary is drawn by who owns the interface. Getting package/module structure right (interfaces with their clients) is what turns class-level DIP into architectural decoupling; getting it wrong yields code that uses interfaces yet still has dependencies pointing the wrong way.
DIP inverts dependencies so the core depends only on abstractions it owns; OCP says the core should be extendable without modification. Combine them: the core defines stable extension interfaces (DIP-owned abstractions) and is closed to modification, while new behavior arrives as new implementations of those interfaces discovered/wired at the edges (OCP extension). That is exactly a plugin architecture — the host is a stable, abstract core; plugins are volatile details depending inward on the host's contracts, added without recompiling the host. The composition root (or a plugin loader) binds concrete plugins to abstractions at startup. So DIP provides the direction (depend on abstractions, details plug in) and OCP provides the goal (extend by adding, not editing), and their intersection is the plugin/clean-architecture model where the valuable core never changes to accommodate new integrations.