skip to content

Dependency Direction

Almost every architectural style is a rule about which way arrows may point: toward policy, never from stable to volatile, never in a cycle. You will connect the Dependency Rule, plugin architecture, acyclic dependencies and stable abstractions into one coherent story.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

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?

level: juniorimportance: must knowfreq 82%

answer

  1. policy owns the interface
  2. control flows down, source arrows point up
  3. abstractions must not name details
  4. invert across volatility, not everywhere
  5. composition root knows both sides

basics

~20 s

DIP 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 s

The 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
kotlin
// --- 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

for a junior

State both clauses, give the interface-in-the-middle picture, and name one concrete swap (Postgres → in-memory fake for tests).

for a middle

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.

for a senior

Separate DIP from DI and IoC, discuss the composition root, and give explicit criteria for when NOT to invert (volatility-based, not reflex).

for a principal

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'.

context

open as a page

Clean/Onion/Hexagonal architectures all state a Dependency Rule: source dependencies may only point one way. Which way, why that way, and how does it turn infrastructure into a plugin?

level: middleimportance: must knowfreq 68%

basics

~20 s

Source dependencies point inward, toward policy: UI and database depend on use cases, use cases depend on the domain, and nothing inner knows anything outer. Because the inner circles define the interfaces, outer pieces plug into them and can be swapped like plugins.

open as a page

What is the Acyclic Dependencies Principle, what concrete problems do dependency cycles between components cause, and what are the two standard ways to break a cycle?

level: middleimportance: should knowfreq 52%

basics

~20 s

The Acyclic Dependencies Principle says the component dependency graph must have no cycles. Cycles make components impossible to build, test, or release separately. You break them either by inverting one edge with an interface, or by extracting the shared part into a new component both depend on.

open as a page

Distinguish source-code, build/binary, and runtime dependency direction. When a service calls another service over HTTP, in what sense can that dependency still be 'inverted', and who should own the contract?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Source dependency = whose code names whose; build dependency = which artifact must be present to compile/link; runtime dependency = who calls whom and must be running. Over HTTP the caller still needs the callee at runtime, but you can invert the source/contract direction by having the consumer define the interface it needs and the provider satisfy it.

open as a page

Explain the Stable Dependencies Principle and the Stable Abstractions Principle, including the instability (I) and abstractness (A) metrics, the Main Sequence, and the Zone of Pain and Zone of Uselessness.

level: seniorimportance: should knowfreq 34%

basics

~20 s

SDP: depend in the direction of stability — a component should only depend on components harder to change than itself. SAP: the more stable a component, the more abstract it should be. Instability I = outgoing/(incoming+outgoing) dependencies; abstractness A = abstract types / all types. Good components sit near the line A + I = 1.

open as a page

Dependency inversion has real costs. As the architect of a large system, how do you decide which dependencies to invert, and how do you keep the chosen dependency direction from eroding over time?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Invert only where volatility crosses an important boundary — vendors, I/O, the clock, anything you might replace. Skip it for stable, single-implementation code. Then make direction machine-enforced: separate build artifacts and architecture tests in CI, not code-review discipline.

open as a page