In a typical layered design, the business logic calls the database code directly. The high-level policy depends on the low-level detail. Change the database, and you have to touch the business logic. Test the business logic, and you need a database.
The Dependency Inversion Principle (DIP) reverses that. Robert C. Martin described it in the C++ Report in June 1996, and it's the D in SOLID. It has two parts:
1. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces)
2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions
**What gets "inverted"?**
The direction of the source code dependency. Without DIP, `OrderService` imports `PostgresOrderRepository`. With DIP, `OrderService` depends on an `OrderRepository` interface, and `PostgresOrderRepository` implements it. That interface belongs to the high-level side, so it speaks the language of the business, not the language of the database. The low-level code now depends on the high-level abstraction, instead of the other way around.
**DIP vs. dependency injection**:
The two often get confused. They're related, but they answer different questions:
- **Dependency inversion** is a design principle: which way your dependencies point
- **Dependency injection** is a technique: how a component receives its dependencies (through a constructor, a setter or a framework) instead of creating them itself
Dependency injection is a common way to apply DIP, because something has to hand the concrete implementation to the code that only knows the abstraction. But you can inject concrete classes without inverting anything, and you can invert dependencies without any DI framework.
**Why it matters**:
- **Replaceable details**: Swap the database, the email provider or the payment gateway without touching the business rules
- **Testability**: Test the high-level logic with simple fakes instead of real infrastructure
- **A stable core**: The most important code depends on nothing volatile
- **Clean boundaries**: It's the mechanism behind Clean Architecture, hexagonal architecture and ports and adapters
**Keep in mind**:
Putting an interface in front of every class doesn't make a design loosely coupled. The value comes from thinking about the interaction between modules, and shaping the abstraction around what the high-level side needs. Depending on stable details is fine, too: nobody wraps the standard library's string type in an interface.
**In AI skills**:
In AI skills, tight coupling manifests as hardcoded tool references. Skills should depend on abstractions, not concrete implementations. A skill welded to specific tools (e.g., a particular CLI command or a specific search tool) breaks as soon as one of them changes or gets replaced. The fix is to describe intent instead of implementation: "Search vault", not "call qmd". The harness then resolves that intent to whatever tool is available.