The idea
What it is
The Dependency Inversion Principle is the D in SOLID. Its job is to stop your most important code — the high-level policy, the part that holds the business rules — from being glued to a specific tool it happens to use. Instead of the policy reaching down and naming a concrete tool, you put an interface in the middle and let both sides depend on that interface.
Picture the power socket in your wall. Your laptop charger doesn't depend on the power plant — it depends on the socket standard. As long as something behind the wall delivers that standard, the charger neither knows nor cares whether the electricity comes from coal, a nuclear plant, or the solar panels you just installed. You can swap the entire power source and never touch your charger. DIP asks you to design code the same way: depend on the socket (an interface), not on the plant (a concrete class).
The one sentence to remember
High-level policy and low-level details should both depend on an abstraction (an interface) — not on each other. The policy owns the socket; the details plug into it.
DIP is not Dependency Injection
These get mixed up constantly. DIP is the principle — a goal about which way your dependencies should point (toward abstractions). Dependency Injection (DI) is just one technique for reaching that goal: instead of a class building its own dependency with new, you hand the dependency to it from outside (usually through the constructor). You can follow DIP without a DI framework, and you can use DI without truly following DIP. DIP = the why; DI = one how.
Mechanics
How it works
The two formal rules
Robert C. Martin states DIP as two rules. They sound abstract; we'll make them concrete in a moment.
- Rule 1 — High-level modules should not depend on low-level modules. Both should depend on abstractions. (Your
OrderServiceshouldn't depend onMySQLDatabase; both should depend on anOrderRepositoryinterface.) - Rule 2 — Abstractions should not depend on details. Details should depend on abstractions. (The interface shouldn't mention MySQL-specific types; instead,
MySQLOrderRepositoryis the one that bends to fit the interface.)
The classic smell: a service that `new`s its own tool
Here is the trap, and almost everyone writes it at first. A high-level class builds the concrete tool it needs inside itself:
class OrderService { // high-level policy
private email = new EmailSender(); // ⚠️ welded to a concrete detail
placeOrder(order: Order) {
// ...business rules...
this.email.send(order.customer, "Your order is confirmed");
}
}That single new EmailSender() line quietly causes two real problems. You can't swap it — the day product wants SMS instead of email, you have to open OrderService and edit business-critical code just to change a delivery channel. And you can't test it — every test of OrderService now fires a real email, because the service insists on building the real sender. The policy has been welded to a detail it should never have cared about.
The fix: depend on an interface, inject the detail
Introduce an interface that describes what the policy needs — MessageSender with a send(...) method — and have OrderService hold one of those instead of a concrete sender. Then pass the real implementation in from outside, through the constructor:
interface MessageSender { // the abstraction (the "socket")
send(to: string, text: string): void;
}
class OrderService {
constructor(private sender: MessageSender) {} // injected from outside
placeOrder(order: Order) {
// ...business rules...
this.sender.send(order.customer, "Your order is confirmed");
}
}
class EmailSender implements MessageSender { /* ... */ } // a detail
class SMSSender implements MessageSender { /* ... */ } // another detail
new OrderService(new EmailSender()); // wire it up at the edge of the appWhere the "inversion" actually is
The name confuses people, so look closely at the arrows. Before, the dependency arrow ran downward: policy → detail (OrderService → MySQLDatabase). The high-level code pointed at, and was at the mercy of, the low-level code. After, that arrow is inverted: the detail now points up at the abstraction (MySQLOrderRepository → OrderRepository), and the policy points at the same abstraction. The crucial twist is that the interface is owned by the high-level side — it's defined in terms of what the policy needs, and the detail must conform to it. The detail now serves the policy, not the other way around. That reversal of who-conforms-to-whom is the 'inversion'.
OrderService points straight down at the concrete MySQLDatabase (filled arrowhead = a hard dependency). After, an «interface» OrderRepository sits in the middle. OrderService depends on it, and MySQLOrderRepository implements it — so the detail's arrow is now inverted, pointing up (open arrowhead) toward the abstraction the policy owns. Same work gets done; the dependency now flows the other way.Why this is the whole point
- Swapping — to go from email to SMS you write a new
SMSSender implements MessageSenderand change one wiring line at the edge of the app.OrderServiceis never opened. - Testing — in a unit test you inject a
FakeSenderthat just records what it was asked to send. The test is fast, offline, and asserts on the recording — no real email, no real database. - Decoupling — the policy and the detail no longer know each other's names. Either can change independently as long as the interface holds. That's loose coupling, and it's what keeps a large codebase soft enough to change.
Who owns the interface matters
DIP isn't just 'use an interface somewhere'. The abstraction should be defined by, and live with, the high-level policy — it describes what the policy needs (MessageSender.send), in the policy's language. The low-level detail then conforms to it. If instead the interface mirrors a specific tool's API, you've only added indirection, not inverted anything.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
Two modes of the same OrderService. In Welded, the service news an EmailSender inside itself — the dependency arrow points straight down at the concrete class (in danger red), and the side panel shows swap? ✗ and testable? ✗. Flip to Inverted and an «interface» MessageSender slides in between them; now a tray lets you plug in EmailSender, SMSSender, or FakeSender (test). Each pick inverts the bottom arrow to point up at the interface, flips both indicators to green ✓, and the fixed panel narrates the swap — OrderService never changed. One panel, replaced each click; nothing scrolls away.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Feel the weld
Open the prototype in Welded mode. The OrderService box literally contains new EmailSender(), and a red dependency arrow points straight down at the concrete EmailSender. Read the side panel: swap? ✗, testable? ✗. Notice the tray of other senders is disabled — there is no way to plug anything else in, because the service builds its own. This is the smell DIP fixes.
Invert the arrow
Flip to Inverted mode. Watch an «interface» MessageSender slide in between the service and the implementations, and watch the bottom arrow invert to point up at the interface. Both indicators flip to green: swap? ✓, testable? ✓. Nothing inside OrderService changed — it now depends on the socket, not the plant.
Swap and fake without touching the service
In Inverted mode, plug in SMSSender from the tray — the explain panel confirms OrderService never changed. Now plug in FakeSender (test) and read why that makes the service testable: the fake just records the message, so a unit test runs with no real email or network. Click between the three implementations and confirm the service box stays identical every time — that is the payoff of inverting the dependency.
In practice
When to use it — and what trips people up
Invert the dependencies that are likely to change
DIP earns its keep around volatile or external dependencies — anything that talks to the outside world or that you might reasonably want to replace, fake, or reconfigure. These are exactly the things that make code hard to test and hard to evolve when you new them inline.
- Databases / persistence —
OrderRepositoryinterface in front of MySQL, Postgres, or an in-memory store for tests. - Network & third-party services — payment gateways, email/SMS providers, an HTTP client to another team's API.
- The clock and randomness — wrap
now()andrandom()behind an interface so tests can pin time and seeds instead of being flaky. - Anything you want to fake in a unit test — if you can't test a class without standing up real infrastructure, that dependency wants inverting.
Don't invert stable, never-changing dependencies
DIP is a tool, not a tax on every line. Don't wrap the standard library, simple value objects (a Money, a Point), String, or math functions behind interfaces — they don't vary, you'll never fake them, and you'll never swap them. Inverting a stable dependency just adds an extra hop and an empty interface that confuses the next reader. Invert what is volatile; depend directly on what is stable.
What it gives you
- Testability — inject a fake or mock and unit-test the policy with no database, network, or email in sight.
- Swappability — change Email→SMS, MySQL→Postgres, or real→stub by editing one wiring line; the high-level code is never reopened.
- Decoupling — policy and details stop knowing each other's names, so each can evolve independently behind a stable interface.
- Parallel work & boundaries — once the interface is agreed, two people can build the policy and the implementation at the same time.
Common mistakes
- More moving parts — every inverted dependency adds an interface plus a concrete class, which is more to read and navigate.
- Wiring overhead — something must construct and connect the pieces at the app's edge (a main()/composition root or a DI container), and that wiring can sprawl.
- Indirection cost — 'go to definition' lands on the interface, not the real code, which can slow down tracing a call for the first time.
- Over-application — inverting stable or one-off dependencies adds ceremony with no payoff; DIP misused turns simple code into interface soup.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// BEFORE — OrderService is welded to a concrete EmailSender.
class EmailSenderV0 {
send(to: string, text: string) { /* ...real email... */ }
}
class OrderServiceV0 {
private email = new EmailSenderV0(); // ⚠️ can't swap, can't test
placeOrder(customer: string) {
this.email.send(customer, "Order confirmed");
}
}
// AFTER — depend on an interface, inject the detail from outside.
interface MessageSender {
send(to: string, text: string): void;
}
class OrderService {
constructor(private sender: MessageSender) {} // injected
placeOrder(customer: string) {
this.sender.send(customer, "Order confirmed");
}
}
// Swap Email -> SMS by changing ONE wiring line. OrderService untouched.
class EmailSender implements MessageSender {
send(to: string, text: string) { /* ...real email... */ }
}
class SMSSender implements MessageSender {
send(to: string, text: string) { /* ...real SMS... */ }
}
const prod = new OrderService(new SMSSender()); // was new EmailSender()
// Inject a FAKE in a test — fast, offline, asserts on a recording.
class FakeSender implements MessageSender {
sent: { to: string; text: string }[] = [];
send(to: string, text: string) { this.sent.push({ to, text }); }
}
const fake = new FakeSender();
new OrderService(fake).placeOrder("ada@x.io");
// expect(fake.sent[0].text).toBe("Order confirmed");References & further reading
5 sources- Docsen.wikipedia.org
Dependency inversion principle — Wikipedia
Crisp definition of both formal rules, the relationship to SOLID, and a clear treatment of how DIP differs from (but is implemented via) dependency injection.
- Articlemartinfowler.com
Inversion of Control Containers and the Dependency Injection pattern — Martin Fowler
The definitive essay on the technique most often used to achieve DIP. Fowler untangles Inversion of Control from Dependency Injection and walks through constructor vs. setter injection with examples.
- Articlebaeldung.com
The Dependency Inversion Principle in Java — Baeldung
A hands-on, code-first walkthrough that refactors a welded service into an inverted one, then shows how Spring wires the implementation in. Great second read for Java developers.
- Articlestackify.com
SOLID Design Principles Explained: Dependency Inversion — Stackify
Beginner-friendly explanation built around a coffee-machine example, with before/after code that makes the 'invert the arrow' idea click.
- Talkyoutube.com
Dependency Inversion Principle Explained — SOLID Design Principles (video)
A short, visual run-through of DIP with live refactoring — good if you want to see the dependency arrow flip on screen before reading the formal rules.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Sign in to save your best score.
question 01 / 05
What does the Dependency Inversion Principle actually say?
question 02 / 05
An OrderService contains private email = new EmailSender(). Which two problems does this directly cause?
question 03 / 05
What is the difference between the Dependency Inversion Principle and Dependency Injection?
question 04 / 05
After applying DIP, what does the 'inversion' refer to — what changed about the dependency arrow?
question 05 / 05
Which dependency is the BEST candidate to leave alone rather than invert behind an interface?
0/5 answered