What is the Dependency Inversion Principle (DIP), and how does introducing an interface change which way a dependency points between a high-level policy and a low-level detail?
answer
- policy owns the interface
- control flows down, source arrows point up
- abstractions must not name details
- invert across volatility, not everywhere
- composition root knows both sides
basics
~20 sDIP says high-level policy should not depend on low-level details; both should depend on an abstraction. You define an interface next to the policy, the policy calls it, and the detail implements it — so the arrow now points from the detail to the policy.
solid answer
~50 sThe Dependency Inversion Principle states: (1) high-level modules must not depend on low-level modules, both should depend on abstractions; (2) abstractions must not depend on details, details depend on abstractions. Concretely, an order-processing policy that directly imports a PostgresOrderStore has a source dependency on infrastructure: changing the database forces recompiling and possibly editing the policy. Under DIP, the policy declares an OrderStore interface it owns, expressed in its own vocabulary, and calls only that. The Postgres class implements the interface, so the compile-time arrow now runs from infrastructure into the policy — inverted relative to the call direction, which still flows policy → database. The wiring (which implementation to use) is pushed to a composition root at startup, typically via constructor injection. The payoff: the volatile, replaceable thing depends on the stable, valuable thing, so policy can be compiled, tested with a fake, and reasoned about without infrastructure.
code
kotlin · 14 lines// --- policy module (high level) — owns the abstraction ---
interface OrderStore {
fun ordersAwaitingShipment(): List<Order> // domain words, no SQL/ResultSet
}
class ShippingPolicy(private val store: OrderStore) { // injected, not constructed
fun run() = store.ordersAwaitingShipment().forEach { ship(it) }
}
// --- infrastructure module (low level) — imports policy, not vice versa ---
class PostgresOrderStore(db: Db) : OrderStore { /* SQL lives only here */ }
// --- composition root: the only place that knows both ---
fun main() = ShippingPolicy(PostgresOrderStore(db)).run()go deeper
State both clauses, give the interface-in-the-middle picture, and name one concrete swap (Postgres → in-memory fake for tests).
Emphasize that the interface is owned by the caller and expressed in domain vocabulary, and that control flow and source dependency now point opposite ways.
Separate DIP from DI and IoC, discuss the composition root, and give explicit criteria for when NOT to invert (volatility-based, not reflex).
Frame it as protecting the stable/valuable from the volatile/replaceable, tie it to build times, independent testability and deployability, and discuss leaky abstractions where inversion cannot actually hide the mechanism.
## The terms first - **Module / component**: any unit of code with a name — a class, a package, a library, a service. Nothing language-specific. - **Dependency**: module A *depends on* module B if A must know B's name to compile/load. In most languages this shows up as an `import`/`using`/`require` of B inside A. - **High-level module (policy)**: code expressing *business decisions* — "a customer with 3 late payments is blocked". It is the reason the system exists and changes for business reasons. - **Low-level module (detail)**: code about *mechanism* — SQL, HTTP, file formats, a vendor SDK, the clock. It changes for technical reasons (version bumps, vendor swaps) far more often. - **Abstraction**: a name with a contract but no mechanism — an interface, an abstract class, a function type, a protocol. ## The problem DIP solves Naively, calls and dependencies point the same way. Policy calls the database, so policy imports the database module: ``` OrderService ──imports──▶ PostgresOrderStore ──imports──▶ vendor driver ``` Consequences: 1. **Change amplification.** Swapping storage, or a driver's breaking change, ripples up into the most valuable code. 2. **Untestability.** You cannot exercise the policy without a real database. 3. **Build/deploy coupling.** Policy cannot compile or ship without the infrastructure library present. 4. **Vocabulary leakage.** Infrastructure types (result sets, HTTP responses) infect business signatures. Note the asymmetry: the *stable, valuable* thing depends on the *volatile, replaceable* thing. DIP flips exactly that. ## The two clauses (Robert C. Martin's formulation) 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.** Clause 2 is the one people drop, and it is where most fake DIP dies: if your `OrderStore` interface has a method `ResultSet findAll()`, or a `save(row: DatabaseRow)`, the abstraction depends on a detail and nothing was inverted. ## The mechanism, step by step 1. Look at what the policy *needs*, not at what the database *offers*. Express it in domain words: `fun ordersAwaitingShipment(): List<Order>`. 2. Declare that interface **inside the policy's own module** — ownership matters (see below). 3. The policy calls only through the interface and receives it from outside (constructor parameter), never constructing an implementation itself (`new PostgresOrderStore()` inside policy re-creates the dependency). 4. The infrastructure module imports the policy module to implement the interface. 5. A **composition root** — `main`, a DI container config, a module wiring file — is the one place that knows both sides and connects them. Result: ``` OrderService ──▶ «interface» OrderStore ◀──implements── PostgresOrderStore (compile-time arrows) OrderService ─────── calls at runtime ──────▶ PostgresOrderStore ``` The **flow of control** still goes policy → database. The **source dependency** now goes database → policy. Where those two disagree, you have crossed a boundary and "inverted" a dependency. That gap between call direction and dependency direction is the entire trick; everything else (ports and adapters, plugin architecture, the Dependency Rule) is this trick applied repeatedly. ## Ownership: who declares the interface The interface must live with the **client** (the policy), not with the implementer. If the infrastructure package declares `OrderStore` and the policy imports it, the source arrow still runs policy → infrastructure and you have gained a level of indirection with none of the decoupling. "Client owns the interface" is the diagnostic question that separates real DIP from ceremonial DIP. ## What NOT to invert DIP is a tool with a cost — one more type, one more indirection, one more hop when reading code. Invert across **volatility boundaries**, not everywhere: - Do not invert away from your language's standard library primitives (strings, collections, math). They are stable; an interface buys nothing. - Do not invert a dependency with exactly one implementation that will never change and is already in the same deployable unit — that is speculative generality. - Do invert on: databases, message brokers, third-party APIs, the file system, the clock and random source, email/SMS gateways, anything with a vendor's name on it. ## Related but distinct - **Dependency injection (DI)** is a *technique for supplying* collaborators. You can inject a concrete class — that is DI without inversion. DIP is about *what type the parameter is declared as*. - **Inversion of control (IoC)** is the broader idea that a framework calls your code rather than the reverse. DIP is one specific IoC pattern applied to source dependencies. - A **DI container** is optional plumbing. Manual constructor wiring in `main` satisfies DIP perfectly. ## Costs and honest trade-offs - Indirection makes "jump to implementation" ambiguous and stack traces longer. - Over-inversion produces interfaces with one implementation and identical shape — a "header interface" that is just a copy of a class, adding names without adding options. - Leaky abstractions: if the interface cannot hide the mechanism (transactions, streaming, pagination, partial failure), you may end up with an abstraction that only ever works with one implementation anyway. Be explicit about that instead of pretending.
- Is dependency injection the same thing as dependency inversion?No. Injection is how a collaborator is supplied (constructor/setter/container); inversion is about the declared type being an abstraction owned by the client. Injecting a concrete `PostgresOrderStore` is DI with zero inversion.
- If I put the interface in the infrastructure package instead, what breaks?The source arrow still runs policy → infrastructure, so the policy still cannot compile, ship, or be tested without infrastructure. You get indirection without decoupling — the classic ceremonial DIP.
- Should every class get an interface?No. Invert where volatility crosses a boundary (vendors, I/O, clock, network). One-implementation 'header interfaces' mirroring a stable class add names and indirection with no optionality.
A wall socket. The lamp maker doesn't build the power plant, and the power plant doesn't know about your lamp — both conform to the socket standard. The standard is defined by the side that must stay stable, and appliances (the volatile, replaceable side) are the ones that must adapt.
saying these in an interview costs you the question
- Saying DIP and dependency injection are the same thing.
- Declaring the interface in the infrastructure package and calling it inversion.
- Defining the interface from what the database offers (findAll, save) instead of what the policy needs.
- Letting infrastructure types (ResultSet, HTTP request, ORM entity) appear in the interface signature.
- Constructing the concrete implementation inside the policy while claiming it is decoupled.
- Claiming inversion removes the runtime need for the database.
- Adding an interface for every class 'because DIP'.