# Dependency Inversion

Depending on abstractions rather than concrete implementations, so high-level policy does not depend on low-level detail.

**Rests on:** Layered Architecture

**Leads to:** Hexagonal Architecture

*Software Engineering · Software Architecture*


## Why learn this

You need this principle to build software that does not collapse under its own weight. Imagine you are writing a program to calculate monthly sales tax. Today it uses one specific rule set for California. Next month, you must add rules for New York. Without this concept, every change to the tax logic forces you to open every file in the project, check if it touches the calculation, and hope you did not break something else. With it, you change only the file containing the New York rules. The rest of your program stays untouched. This is how large systems survive: by keeping the core business logic isolated from external tools like databases or payment gateways. If you swap a database from Postgres to MySQL, or a payment provider from Stripe to PayPal, only one small piece changes. The rest of your application does not care what tool is used, only that it works according to the contract. This isolation makes testing fast and easy because you can plug in fake tools instead of real ones. It turns a fragile house of cards into a sturdy box where parts can be swapped without rebuilding the structure.


## The intuition

Consider a household toaster. The heating element (the low-level detail) knows exactly how to get red-hot coils warm. But the toaster does not tell the heating element what to do. Instead, the user interface on the side — the lever you push down and the dial you turn (the high-level policy or abstraction) — dictates the experience. You define a 'slot' where bread goes. The heating element must fit that slot. If you buy a new toaster with a ceramic plate instead of wire coils (a different low-level detail), it still works, provided it fits the same 'slot' definition. The core function — toasting bread — is defined by the user interface, not the heating method.

In code, this looks like this: You write a piece of logic that says 'send an email to confirm the order.' This is your high-level policy. Normally, you might be tempted to write 'use Amazon SES' or 'use SendGrid' right inside that logic. But if you do, you are tied to those specific services. Instead, you define an interface (a set of rules) called 'EmailSender'. Your confirmation logic says: 'Call the EmailSender interface.' It does not know if the email goes out via SMTP, a cloud API, or even a printer that prints emails for someone to mail physically. The concrete classes that actually send the email (the low-level details) must implement the 'EmailSender' interface. They adapt themselves to fit the contract defined by your high-level logic. The dependency is inverted: instead of your business logic depending on the specific email service, both your business logic and the email service depend on the shared interface.


## An analogy

Think of a universal power adapter. Your laptop (the high-level policy) needs electricity to function. In the past, laptops came with thick, proprietary bricks that only worked with one wall outlet in one country. The laptop was tightly coupled to that specific power source. Today, most laptops use USB-C ports. You can plug them into a generic power bank, a wall charger, or a solar panel. The laptop defines the shape and voltage of the 'USB-C port' (the abstraction). All external power sources must conform to this standard. If you buy a faulty cable that does not meet the USB-C standard, your laptop will not charge properly — just as code fails if an implementation violates the interface contract. The key is that the laptop manufacturer does not care where the electricity comes from; they only care that it arrives through the standard port they designed.

The analogy has a limit: in software, the 'port' is purely logical and can change instantly, whereas you cannot physically unplug a wall outlet while the laptop is running without a brownout. But structurally, the principle holds: the central device dictates the connection point to remain independent of the energy source.


## Definition

Dependency inversion is the design principle where high-level modules should not depend on low-level details; instead, both must depend on abstractions (interfaces or contracts) that are defined by the high-level modules.

connections


## How it connects

This concept is the bedrock of Hexagonal Architecture, which separates the core business rules from the outside world using 'ports' (the interfaces you define) and 'adapters' (the code that connects to databases or APIs). It also enables Clean Architecture by ensuring that inner layers know nothing about outer layers. This separation directly supports the Single Responsibility principle, because your email-sending code can change without affecting your order-processing code. It is closely related to the Strategy Pattern, which allows you to swap algorithms at runtime, and Inversion of Control, a framework technique that handles the wiring of these dependencies for you.


## The mathematics

This concept is not mathematical in the traditional sense; it is about the direction of arrows in a dependency diagram. But we can use a small amount of arithmetic to illustrate substitution. Imagine you have a function that sends a notification. It could send by email, by SMS, or by push. Each method has a cost. Email costs 1 unit, SMS costs 2, push costs 3. The high-level policy (say, a 'send reminder' routine) does not care which one is used; it just calls a generic 'send' function. If you have 10 reminders to send, and you use email, the total cost is 10 × 1 = 10. If you switch to SMS, the total cost is 10 × 2 = 20. The high-level policy does not change — it still calls 'send' 10 times. The total cost depends on which low-level implementation you plug in, but the high-level policy's code stays the same. This is the arithmetic of substitution.

Let H be a high-level policy and L a low-level detail. The principle states: H depends on A (an abstraction) and L depends on A, so H never depends on L directly. In code, this is expressed as: define an interface A; H calls methods of A; L implements A. The dependency arrows point from H and L to A, not from H to L.


## A worked example

```
Suppose you have a high-level function 'send_reminders(count)' that calls a 'send' method. The count is the number of reminders, say 10. The cost of each send depends on the implementation: email costs 1, SMS 2, push 3.

Step 1: cost_per_send = 1 (email)   # Choose the email implementation.
Step 2: total_cost = 10 * 1 = 10   # Send 10 reminders via email.
Step 3: cost_per_send = 2 (SMS)   # Switch to SMS without changing the high-level code.
Step 4: total_cost = 10 * 2 = 20   # The total cost changes, but the high-level code is unchanged.

Result: The high-level policy 'send_reminders' is exactly the same in both cases; only the low-level 'send' implementation changes. This is the arithmetic of dependency inversion: the high-level code does not need to know the cost multiplier; it just calls 'send'.
```


## From scratch, in Python

```python
class NotificationSender:
    """The abstraction (interface) that defines what sending looks like."""
    def send(self, message):
        raise NotImplementedError

class EmailSender(NotificationSender):
    """A concrete implementation that sends via email."""
    def send(self, message):
        print(f"Sending email: {message}")

class SMSSender(NotificationSender):
    """A concrete implementation that sends via SMS."""
    def send(self, message):
        print(f"Sending SMS: {message}")

def send_reminders(sender, message):
    """High-level policy: sends reminders but doesn't care how."""
    sender.send(message)

# Usage
email_notifier = EmailSender()
sms_notifier = SMSSender()

send_reminders(email_notifier, "Your appointment is tomorrow")
send_reminders(sms_notifier, "Your appointment is tomorrow")
```


## What the code does

We define a class `NotificationSender` that acts as the abstraction (the port). It has a method `send` that raises `NotImplementedError` — meaning any subclass must override it. Then we create two concrete implementations: `EmailSender` and `SMSSender`, each with its own `send` method that does the actual work. The high-level function `send_reminders` takes a `sender` object and calls its `send` method. It does not know whether the sender is email or SMS; it only knows it has a `send` method. This is dependency inversion: the high-level policy depends on the abstraction (`NotificationSender`), and the low-level details also depend on it by inheriting from it. If we want to add a push notification sender, we just create a new class that inherits from `NotificationSender` and implement `send` — no change to `send_reminders`.


## Common misconceptions

**Dependency inversion is the same as dependency injection** — I heard dependency inversion is just about passing dependencies into constructors.

Dependency injection is a technique for providing dependencies from outside, but dependency inversion is a broader architectural principle about the direction of dependencies. You can use dependency injection without inverting dependencies — for example, passing a concrete class into a function still keeps the high-level dependent on the low-level. In fact: Dependency inversion is about ensuring high-level policy does not depend on low-level details; both depend on an abstraction. Dependency injection is one way to achieve that — it passes the abstraction in from outside, but it is not the principle itself.

**Inversion means the low-level depends on the high-level** — Dependency inversion means the low-level code should call the high-level code.

The word 'inversion' refers to reversing the direction of dependency from how it would naturally be. In the natural direction, the high-level code would call the low-level code directly. Inversion means both depend on an abstraction, so neither directly depends on the other. In fact: Both high-level and low-level depend on an abstraction. The inversion is that the abstraction is owned by the high-level policy, not the low-level.

**Interfaces are only for testing** — The main reason to use interfaces is so I can mock things in tests.

While interfaces do enable testing, dependency inversion is about design — it keeps the system flexible and maintainable, allowing substitution of implementations in production as well as in tests. In fact: Interfaces are the mechanism for dependency inversion, which enables substitution, testing, and independent evolution of components.

**Dependency inversion requires a framework** — I need a dependency injection framework like Spring to do dependency inversion.

Dependency inversion is a principle, not a tool. You can implement it in plain Python with classes and inheritance, as shown in the code section. In fact: You can achieve dependency inversion with simple language features — define an interface class and have concrete classes implement it.


## In short

Dependency inversion means high-level policy should not depend on low-level details; both should depend on an abstraction that the high-level policy defines. This makes systems testable, maintainable, and flexible. It is the foundation of ports and adapters, and it is what lets you swap out implementations without touching your core logic.


## Where this is used

In large language models (LLMs), dependency inversion appears in how the model interface is abstracted. For example, an application that uses ChatGPT, Claude, or Llama defines a generic `LLM` interface with a method like `generate(prompt)`. The high-level application logic (e.g., a chatbot conversation loop) calls this interface and does not care which provider is behind it. When switching from OpenAI to Anthropic, you write a new adapter that implements the same interface, and the application code remains unchanged. In small models running on devices, dependency inversion lets you swap between a large cloud-based model and a tiny on-device model without rewriting the app logic. The interface defines a `predict` method, and each model implements it. In retrieval-augmented generation (RAG), the embedding and retrieval machinery is often abstracted. The RAG pipeline depends on an interface like `retrieve(query)` that can be implemented by a vector database, a keyword search, or a hybrid. This allows the high-level question-answering logic to be independent of the underlying retrieval mechanism. In agentic systems that plan and call tools, dependency inversion is crucial: an agent (high-level policy) depends on a `Tool` interface with methods like `execute(input)`. The agent does not know whether a tool is a calculator, a web search, or a database query; it just calls the interface. This makes it easy to add new tools without modifying the agent's planning logic. In everyday applied machine learning, dependency inversion is ubiquitous. A recommendation system depends on a `UserDatabase` interface with methods like `get_user_preferences(id)`. The high-level recommendation algorithm does not care whether the data comes from a SQL database, a CSV file, or an API; it just calls the interface. This makes testing easier — you can substitute a fake database that returns fixed data. In practice, a developer reaches for a framework like Flask or FastAPI for the web layer, but the architecture patterns are language-agnostic. What they write is a set of interfaces (often using ABC classes in Python) and adapters that implement them. When done badly, the result is tight coupling: changing the database provider requires changing every function that directly called the old database, breaking the application and making testing impossible. When done well, the application is modular, testable, and maintainable — a direct payoff of the 'ilities' like maintainability and testability.


---

Read this on the web: https://basavapurushottam.com/learn-ai/dependency-inversion
