What does the Dependency Inversion Principle (DIP) state, and what exactly is being "inverted"?
answer
- two clauses: both depend on abstractions; abstractions don't depend on details
- source dependency points against flow of control
- policy owns the interface, detail implements it
- boundary = arrow direction flips
- goal, not mechanism (DI/IoC realize it)
basics
~20 sDIP says important policy code must not depend on low-level detail code; both depend on an abstraction (an interface). What gets inverted is the source-code dependency: the detail now points at the abstraction instead of the policy pointing at the detail.
solid answer
~50 sDIP has two clauses. (1) High-level modules (business policy) should not depend on low-level modules (details like a database driver, SMTP client, file system); both should depend on abstractions. (2) Abstractions should not depend on details; details should depend on abstractions. "Inversion" is relative to the naive layered design where policy calls and therefore imports the detail. After DIP, the policy declares an interface describing what it needs ("SendNotification"), and the low-level module implements it. Control still flows from policy into the detail at runtime, but the compile-time/source dependency now points the other way — from the detail up to the abstraction. That lets you replace, test-double, or defer the detail without editing or recompiling the policy, and it puts the stable business rules at the centre of the dependency graph rather than at its mercy.
code
pseudocode · 20 lines// BEFORE — policy names the detail
package billing
import infra.SmtpMailer // policy -> detail (bad)
class OrderService {
fun place(o: Order) { SmtpMailer().send(o.email, "...") }
}
// AFTER — policy owns the abstraction, detail depends on it
package billing
interface Notifier { fun notify(to: Address, msg: Message) }
class OrderService(private val notifier: Notifier) {
fun place(o: Order) { notifier.notify(o.address, receiptFor(o)) }
}
package infra
import billing.Notifier // detail -> abstraction (inverted)
class SmtpNotifier : Notifier { ... }
// composition root, at the program entry point
main() { OrderService(SmtpNotifier()).place(order) }go deeper
State both clauses in your own words and give one concrete example (service + interface + SMTP implementation). Say that the interface is what both sides depend on.
Add the control-flow vs. source-dependency distinction and mention constructor injection plus a composition root as the wiring mechanism.
Discuss ownership of the abstraction, package/deployment placement, the second clause (no leaky abstractions), and the testability/build-decoupling payoff.
Frame DIP as shaping the dependency graph so volatility flows away from stable policy; connect to boundaries, plugin architecture, independent deployability, and the cost of speculative abstraction.
## The words first - **Module**: any unit of code you can depend on — a class, package, library, service. Nothing language-specific. - **High-level module**: code that expresses *policy* — the rules that justify the system's existence. "An order over $100 gets free shipping." "A user must be notified when their password changes." - **Low-level module**: code that expresses a *detail* — a specific mechanism used to carry out policy. A PostgreSQL driver, an SMTP client, a REST call to a payment provider, a file writer, a UI framework. - **Abstraction**: a declaration of *what* is offered without *how* — an interface, an abstract type, a protocol, a function signature, a trait. Its defining feature is that it can be implemented in more than one way. - **Dependency (source-code sense)**: module A depends on B if A names B — imports it, references its type, calls it by concrete name. If B changes shape, A must be recompiled/rewritten. This is distinct from *runtime* dependency (A needs B present to work) and from *flow of control* (A calls into B while executing). ## The principle Robert C. Martin states it in two clauses: 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.** The second clause is the one people forget. It forbids an interface whose *shape* leaks the mechanism: a `UserRepository` interface with a method `executeSql(query: String)` or `getMongoCursor()` is technically an interface, but it is an abstraction that depends on a detail. Change the storage engine and the interface itself must change, so nothing was actually decoupled. ## What is inverted Take the untreated design: ``` OrderService ──imports──▶ SmtpMailer (policy, high-level) (detail, low-level) ``` Both the *call* and the *source dependency* go left→right. `OrderService` cannot be compiled, read, or tested without `SmtpMailer` and everything it drags in. After DIP: ``` OrderService ──uses──▶ «interface» Notifier ▲ │ implements SmtpMailer ``` The *call* still goes `OrderService → SmtpMailer` at runtime. The *source dependency* now goes `SmtpMailer → Notifier`, i.e. upward, against the flow of control. That mismatch between control-flow direction and source-dependency direction is precisely the "inversion". Anywhere it appears, you have crossed an architectural boundary. ## Why anyone cares - **Substitutability**: swap SMTP for a queue, a stub, or a no-op without touching policy. - **Testability**: policy can be exercised with a fake implementation, no network or database. - **Independent deployability / build times**: the policy package no longer transitively depends on driver libraries. - **Decision deferral**: you can write and validate business rules before choosing a database or a vendor. - **Direction of damage**: volatile details change often; stable policy changes rarely. Without inversion, every detail change ripples upward into the most valuable code. With inversion, the ripple stops at the boundary. ## Costs and honest limits Every inversion buys indirection with a price: one more type to name, an extra hop when reading a stack trace, a wiring step (someone must decide which implementation is used), and the risk of a *speculative* abstraction that is wrong. DIP is not "put an interface in front of everything". You invert across boundaries where *volatility* differs — policy vs. mechanism, own code vs. third party — and you leave stable, non-volatile things (a date type, a math routine, the standard library's list) alone. ## Relationship to the mechanisms DIP is the *goal* (a shape of the dependency graph). **Inversion of Control** is the general style where a framework/composition layer, not your code, decides what gets called and when. **Dependency Injection** is the concrete technique for handing a policy its collaborator from outside (constructor, setter, or method parameter), usually assembled in a *composition root* — a single place at the entry point of the program where concrete types are chosen and wired. DI is the most common way to *realize* DIP, but you can use DI and still violate DIP (inject a concrete class), and you can satisfy DIP without a DI container (hand-wire in `main`).
- If control still flows from the high-level module into the low-level one, in what sense is anything inverted?Only the source-code (compile-time) dependency is inverted. At runtime the policy still calls the detail; but the detail's package is the one that names the abstraction, so the detail can be replaced or recompiled without touching the policy.
- Does adding an interface automatically satisfy DIP?No. If the interface is defined in and owned by the low-level module, or if its method signatures expose the mechanism (SQL strings, HTTP status codes, vendor types), the policy still depends on the detail — just indirectly. The abstraction must be shaped by the caller's needs and owned on the caller's side.
A wall socket. Your lamp does not depend on the power station; the power station and the lamp both conform to the plug standard. The socket shape was defined for the benefit of appliances, and generators had to comply — the dependency points at the standard, not at either concrete side, so you can swap the generator or the lamp independently.
saying these in an interview costs you the question
- "DIP just means use dependency injection" — DI is one mechanism; DIP is a statement about which way source dependencies point.
- "DIP means every class should have an interface" — that produces header-interface noise with no substitutability gain.
- Forgetting the second clause and writing abstractions that leak the mechanism (e.g. a repository interface exposing SQL or a driver cursor).
- Claiming the runtime call direction is what gets inverted.
- Placing the interface in the low-level module's package and believing the dependency was inverted.