> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dependency Inversion Principle

> Depend on abstractions, not concrete things - like plugging into a wall socket!

## 🔌 The DIP Rule

> **"High-level modules should not depend on low-level modules. Both should depend on abstractions."**

Think about how you charge your phone:

* You plug into a **wall socket** (abstraction) 🔌
* You don't wire directly to the power plant! ⚡

The socket is an **interface** - any charger that fits will work!

> [!TIP]
>
> **Simple Rule**: Instead of creating dependencies directly, depend on interfaces/abstractions and get the real thing injected from outside!

**The home appliance analogy:** Your refrigerator does not generate its own electricity. It depends on a standard power interface (the wall outlet). The power company can switch from coal to solar to nuclear -- your fridge does not care because it depends on the *abstraction* (standardized outlet), not the *implementation* (specific power plant). DIP works the same way: your high-level business logic should depend on abstract interfaces, and the concrete implementations (which database, which email provider, which payment gateway) are "plugged in" from the outside.

***

## 🎯 What Does "Inversion" Mean?

Traditional dependency (BAD):

```
HighLevel → creates → LowLevel
```

Inverted dependency (GOOD):

```
HighLevel → depends on → Abstraction ← implements ← LowLevel
```

The control is "inverted" - high-level code doesn't create its dependencies, it receives them!

***

## 🚨 The Problem: Tight Coupling

### ❌ BAD: Creating Dependencies Inside

```python theme={null}
class MySQLDatabase:
    def connect(self):
        print("Connecting to MySQL...")
    
    def query(self, sql):
        print(f"MySQL executing: {sql}")
        return [{"id": 1, "name": "Alice"}]

class UserRepository:
    def __init__(self):
        # 🚨 Directly creates MySQL - tightly coupled!
        self.database = MySQLDatabase()
    
    def find_user(self, user_id):
        self.database.connect()
        return self.database.query(f"SELECT * FROM users WHERE id = {user_id}")

# Problems:
# 1. Can't switch to PostgreSQL without changing UserRepository
# 2. Can't test without a real MySQL database
# 3. Can't use a different database for different environments

# Want to switch to PostgreSQL? Must modify UserRepository!
# Want to test? Must have MySQL running!
```

### ✅ GOOD: Depend on Abstraction

```python theme={null}
from abc import ABC, abstractmethod

# 1. Define the abstraction (interface)
class Database(ABC):
    @abstractmethod
    def connect(self):
        pass
    
    @abstractmethod
    def query(self, sql):
        pass

# 2. Implement concrete versions
class MySQLDatabase(Database):
    def connect(self):
        print("🐬 Connecting to MySQL...")
    
    def query(self, sql):
        print(f"🐬 MySQL: {sql}")
        return [{"id": 1, "name": "Alice"}]

class PostgreSQLDatabase(Database):
    def connect(self):
        print("🐘 Connecting to PostgreSQL...")
    
    def query(self, sql):
        print(f"🐘 PostgreSQL: {sql}")
        return [{"id": 1, "name": "Alice"}]

class MockDatabase(Database):
    """For testing!"""
    def connect(self):
        print("🧪 Mock database connected")
    
    def query(self, sql):
        print(f"🧪 Mock query: {sql}")
        return [{"id": 99, "name": "Test User"}]

# 3. Depend on abstraction, receive via injection
# PRINCIPLE: UserRepository does not know or care whether it is
# talking to MySQL, PostgreSQL, or a mock. It only knows the
# Database interface. This is what "inversion" means -- the
# high-level module defines the interface it needs, and the
# low-level module conforms to it.
class UserRepository:
    def __init__(self, database: Database):  # Accepts ANY Database
        self.database = database
    
    def find_user(self, user_id):
        self.database.connect()
        return self.database.query(f"SELECT * FROM users WHERE id = {user_id}")

# 4. Usage - inject whatever you need!
print("=== Production with MySQL ===")
mysql_repo = UserRepository(MySQLDatabase())
mysql_repo.find_user(1)

print("\n=== Production with PostgreSQL ===")
postgres_repo = UserRepository(PostgreSQLDatabase())
postgres_repo.find_user(1)

print("\n=== Testing with Mock ===")
test_repo = UserRepository(MockDatabase())
test_repo.find_user(1)

# 🎉 Same UserRepository code, different databases!
```

***

## 📧 Real Example: Notification System

### ❌ BAD: Directly Creating Email Client

```python theme={null}
import smtplib

class NotificationService:
    def __init__(self):
        # 🚨 Directly creating SMTP - tight coupling!
        self.server = smtplib.SMTP('smtp.gmail.com', 587)
        self.server.login('user@gmail.com', 'password')
    
    def send(self, to, message):
        self.server.send_message(message, to_addrs=[to])

# Problems:
# - Can't switch to SendGrid, Mailgun, or AWS SES easily
# - Can't test without sending real emails!
# - Gmail credentials hardcoded
```

### ✅ GOOD: Inject Email Provider

```python theme={null}
from abc import ABC, abstractmethod

class EmailProvider(ABC):
    @abstractmethod
    def send(self, to: str, subject: str, body: str):
        pass

class GmailProvider(EmailProvider):
    def __init__(self, username, password):
        self.username = username
        self.password = password
    
    def send(self, to, subject, body):
        print(f"📧 Gmail: Sending to {to}")
        print(f"   Subject: {subject}")
        # Actual SMTP logic here

class SendGridProvider(EmailProvider):
    def __init__(self, api_key):
        self.api_key = api_key
    
    def send(self, to, subject, body):
        print(f"📧 SendGrid: Sending to {to}")
        print(f"   Subject: {subject}")
        # SendGrid API logic here

class AWSEmailProvider(EmailProvider):
    def __init__(self, access_key, secret_key, region):
        self.region = region
    
    def send(self, to, subject, body):
        print(f"📧 AWS SES ({self.region}): Sending to {to}")
        print(f"   Subject: {subject}")
        # AWS SES logic here

class MockEmailProvider(EmailProvider):
    """For testing - doesn't send real emails!"""
    def __init__(self):
        self.sent_emails = []
    
    def send(self, to, subject, body):
        print(f"🧪 Mock: Would send to {to}")
        self.sent_emails.append({"to": to, "subject": subject, "body": body})

class NotificationService:
    def __init__(self, email_provider: EmailProvider):  # 🎯 Inject provider
        self.email = email_provider
    
    def notify_user(self, user_email, message):
        self.email.send(user_email, "Notification", message)

# Usage - swap providers easily!
print("=== Using Gmail ===")
gmail_notifier = NotificationService(GmailProvider("user", "pass"))
gmail_notifier.notify_user("customer@example.com", "Your order shipped!")

print("\n=== Using SendGrid ===")
sendgrid_notifier = NotificationService(SendGridProvider("api-key-123"))
sendgrid_notifier.notify_user("customer@example.com", "Your order shipped!")

print("\n=== Testing ===")
mock = MockEmailProvider()
test_notifier = NotificationService(mock)
test_notifier.notify_user("test@test.com", "Test message")
print(f"Captured {len(mock.sent_emails)} emails!")  # Can verify emails!
```

***

## 💳 Real Example: Payment Processing

```python theme={null}
from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class PaymentResult:
    success: bool
    transaction_id: str
    message: str

class PaymentGateway(ABC):
    @abstractmethod
    def process(self, amount: float, card_token: str) -> PaymentResult:
        pass
    
    @abstractmethod
    def refund(self, transaction_id: str) -> PaymentResult:
        pass

class StripeGateway(PaymentGateway):
    def __init__(self, api_key):
        self.api_key = api_key
    
    def process(self, amount, card_token):
        print(f"💳 Stripe: Processing ${amount}")
        return PaymentResult(True, "stripe_tx_123", "Payment successful")
    
    def refund(self, transaction_id):
        print(f"💳 Stripe: Refunding {transaction_id}")
        return PaymentResult(True, transaction_id, "Refund successful")

class PayPalGateway(PaymentGateway):
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
    
    def process(self, amount, card_token):
        print(f"🅿️ PayPal: Processing ${amount}")
        return PaymentResult(True, "paypal_tx_456", "Payment successful")
    
    def refund(self, transaction_id):
        print(f"🅿️ PayPal: Refunding {transaction_id}")
        return PaymentResult(True, transaction_id, "Refund successful")

class SquareGateway(PaymentGateway):
    def __init__(self, access_token):
        self.access_token = access_token
    
    def process(self, amount, card_token):
        print(f"⬜ Square: Processing ${amount}")
        return PaymentResult(True, "square_tx_789", "Payment successful")
    
    def refund(self, transaction_id):
        print(f"⬜ Square: Refunding {transaction_id}")
        return PaymentResult(True, transaction_id, "Refund successful")

class MockPaymentGateway(PaymentGateway):
    """For testing!"""
    def __init__(self, should_succeed=True):
        self.should_succeed = should_succeed
        self.processed = []
    
    def process(self, amount, card_token):
        self.processed.append(amount)
        if self.should_succeed:
            return PaymentResult(True, "mock_tx_000", "Mock success")
        return PaymentResult(False, "", "Mock failure")
    
    def refund(self, transaction_id):
        return PaymentResult(True, transaction_id, "Mock refund")

# Order service depends on abstraction
class OrderService:
    def __init__(self, payment_gateway: PaymentGateway):  # 🎯 Injected!
        self.payment = payment_gateway
    
    def checkout(self, order_total, card_token):
        print(f"🛒 Processing order for ${order_total}")
        result = self.payment.process(order_total, card_token)
        
        if result.success:
            print(f"✅ Order confirmed! Transaction: {result.transaction_id}")
        else:
            print(f"❌ Order failed: {result.message}")
        
        return result

# 🎉 Easy to switch payment providers!

# Production - use Stripe
order_service = OrderService(StripeGateway("sk_live_xxx"))
order_service.checkout(99.99, "tok_visa")

# Different environment - use PayPal
print()
paypal_service = OrderService(PayPalGateway("client_id", "secret"))
paypal_service.checkout(49.99, "paypal_tok")

# Testing - use mock
print("\n=== Testing ===")
mock = MockPaymentGateway(should_succeed=True)
test_service = OrderService(mock)
test_service.checkout(100.00, "test_token")
print(f"Total processed in tests: ${sum(mock.processed)}")
```

***

## 🏗️ Dependency Injection Patterns

### 1️⃣ Constructor Injection (Most Common)

```python theme={null}
class UserService:
    def __init__(self, user_repo: UserRepository, email_service: EmailService):
        self.users = user_repo
        self.email = email_service
```

### 2️⃣ Setter Injection

```python theme={null}
class UserService:
    def __init__(self):
        self._user_repo = None
    
    def set_repository(self, repo: UserRepository):
        self._user_repo = repo
```

### 3️⃣ Method Injection

```python theme={null}
class UserService:
    def create_user(self, data, email_service: EmailService):
        # Email service passed only when needed
        email_service.send_welcome(data['email'])
```

***

## 🏭 Simple DI Container

```python theme={null}
class Container:
    """Simple dependency injection container"""
    
    def __init__(self):
        self._services = {}
    
    def register(self, interface, implementation):
        self._services[interface] = implementation
    
    def resolve(self, interface):
        if interface not in self._services:
            raise KeyError(f"Service {interface} not registered")
        return self._services[interface]

# Setup container
container = Container()

# Register implementations
if ENVIRONMENT == "production":
    container.register("database", MySQLDatabase())
    container.register("email", SendGridProvider("api_key"))
    container.register("payment", StripeGateway("stripe_key"))
else:
    container.register("database", MockDatabase())
    container.register("email", MockEmailProvider())
    container.register("payment", MockPaymentGateway())

# Resolve dependencies
db = container.resolve("database")
email = container.resolve("email")

# Create services with resolved dependencies
user_repo = UserRepository(db)
notification = NotificationService(email)
```

***

## 📊 DIP Benefits Visualization

```
WITHOUT DIP (Tight Coupling):
┌─────────────────────────────────┐
│         OrderService            │
│                                 │
│  ┌─────────────────────────┐    │
│  │   StripeGateway         │◄───┼── Hardcoded inside!
│  │   (concrete class)      │    │
│  └─────────────────────────┘    │
│                                 │
│  - Can't change gateway         │
│  - Can't test without Stripe    │
│  - Coupled to implementation    │
└─────────────────────────────────┘

WITH DIP (Loose Coupling):
┌─────────────────────────────────┐
│         OrderService            │
│                                 │
│  ┌─────────────────────────┐    │
│  │   PaymentGateway        │◄───┼── Interface (abstraction)
│  │   (abstract interface)  │    │
│  └─────────────────────────┘    │
└─────────────────────────────────┘
           ▲
           │ implements
    ┌──────┼──────┬─────────────┐
    │      │      │             │
┌───┴───┐ ┌┴────┐ ┌┴─────────┐ ┌┴────────┐
│Stripe │ │PayPal│ │  Square │ │MockPay │
└───────┘ └─────┘ └─────────┘ └────────┘

- Easy to swap implementations
- Easy to test with mocks
- Decoupled from details
```

***

## 🧪 Practice Exercise

> **Challenge: Fix the Weather App**
>
> This weather app violates DIP. Fix it!
>
> ```python theme={null}
> import requests
>
> class WeatherApp:
>     def __init__(self):
>         # 🚨 Directly coupled to specific API!
>         self.api_key = "hardcoded_key"
>         self.api_url = "https://api.openweathermap.org/data/2.5/weather"
>
>     def get_weather(self, city):
>         # 🚨 Can't test without calling real API!
>         response = requests.get(
>             self.api_url,
>             params={"q": city, "appid": self.api_key}
>         )
>         data = response.json()
>         return {
>             "city": city,
>             "temp": data["main"]["temp"],
>             "description": data["weather"][0]["description"]
>         }
>
>     def display_weather(self, city):
>         weather = self.get_weather(city)
>         print(f"Weather in {weather['city']}: {weather['temp']}°K")
>         print(f"Description: {weather['description']}")
>
> # Problems:
> # - Can't switch to different weather API (WeatherStack, AccuWeather)
> # - Can't test without internet/real API calls
> # - API key hardcoded
> ```
>
> <details>
>   <summary>Click for Solution</summary>
>
>   ```python theme={null}
>   from abc import ABC, abstractmethod
>   from dataclasses import dataclass
>
>   @dataclass
>   class WeatherData:
>       city: str
>       temperature: float
>       description: str
>       unit: str = "Celsius"
>
>   class WeatherProvider(ABC):
>       @abstractmethod
>       def get_weather(self, city: str) -> WeatherData:
>           pass
>
>   class OpenWeatherMapProvider(WeatherProvider):
>       def __init__(self, api_key: str):
>           self.api_key = api_key
>           self.api_url = "https://api.openweathermap.org/data/2.5/weather"
>
>       def get_weather(self, city: str) -> WeatherData:
>           import requests
>           response = requests.get(
>               self.api_url,
>               params={"q": city, "appid": self.api_key, "units": "metric"}
>           )
>           data = response.json()
>           return WeatherData(
>               city=city,
>               temperature=data["main"]["temp"],
>               description=data["weather"][0]["description"]
>           )
>
>   class WeatherStackProvider(WeatherProvider):
>       def __init__(self, api_key: str):
>           self.api_key = api_key
>
>       def get_weather(self, city: str) -> WeatherData:
>           # WeatherStack API implementation
>           print(f"🌤️ WeatherStack: Fetching {city}")
>           return WeatherData(city=city, temperature=22.0, description="Partly cloudy")
>
>   class MockWeatherProvider(WeatherProvider):
>       """For testing!"""
>       def __init__(self, mock_data: dict = None):
>           self.mock_data = mock_data or {}
>           self.calls = []
>
>       def get_weather(self, city: str) -> WeatherData:
>           self.calls.append(city)
>           if city in self.mock_data:
>               return self.mock_data[city]
>           return WeatherData(city=city, temperature=20.0, description="Mock weather")
>
>   class WeatherApp:
>       def __init__(self, weather_provider: WeatherProvider):  # 🎯 Injected!
>           self.provider = weather_provider
>
>       def display_weather(self, city: str):
>           weather = self.provider.get_weather(city)
>           print(f"\n🌍 Weather in {weather.city}")
>           print(f"🌡️  Temperature: {weather.temperature}°{weather.unit}")
>           print(f"☁️  Description: {weather.description}")
>
>   # Production - use OpenWeatherMap
>   api_key = "your_api_key"
>   app = WeatherApp(OpenWeatherMapProvider(api_key))
>   # app.display_weather("London")
>
>   # Different provider - use WeatherStack
>   print("=== Using WeatherStack ===")
>   app2 = WeatherApp(WeatherStackProvider("other_key"))
>   app2.display_weather("Paris")
>
>   # Testing - use mock
>   print("\n=== Testing ===")
>   mock = MockWeatherProvider({
>       "Tokyo": WeatherData("Tokyo", 25.0, "Sunny"),
>       "Moscow": WeatherData("Moscow", -5.0, "Snowing")
>   })
>   test_app = WeatherApp(mock)
>   test_app.display_weather("Tokyo")
>   test_app.display_weather("Moscow")
>   test_app.display_weather("Unknown City")  # Uses default mock
>
>   print(f"\n✅ API was called {len(mock.calls)} times: {mock.calls}")
>   ```
> </details>

***

## 📝 Key Takeaways

| Without DIP | With DIP |
| - | - |
| `new` keyword everywhere | Inject dependencies |
| Tied to specific implementations | Tied to abstractions |
| Hard to test | Easy to mock |
| Hard to change | Easy to swap |
| Tight coupling | Loose coupling |

***

## 🎉 SOLID Complete!

Congratulations! You've learned all five SOLID principles:

> **[S - Single Responsibility](https://resources.devweekends.com/lld/solid/srp.md)**
>
> One class, one job

> **[O - Open/Closed](https://resources.devweekends.com/lld/solid/ocp.md)**
>
> Add features without changing code

> **[L - Liskov Substitution](https://resources.devweekends.com/lld/solid/lsp.md)**
>
> Children replace parents

> **[I - Interface Segregation](https://resources.devweekends.com/lld/solid/isp.md)**
>
> Many small interfaces

> **[D - Dependency Inversion](https://resources.devweekends.com/lld/solid/dip.md)**
>
> Depend on abstractions

***

## Why DIP Matters in Production

DIP is what makes your system testable, deployable, and adaptable. Consider a payment processing service at a startup. In development, you inject a `MockPaymentGateway` so tests run in milliseconds without hitting real APIs. In staging, you inject a `StripeSandboxGateway` that talks to Stripe's test environment. In production, you inject `StripeProductionGateway`. The `OrderService` code is identical across all three environments -- only the injected dependency changes. This is also how feature flags work: you can inject a `NewPricingEngine` for beta users and `LegacyPricingEngine` for everyone else, controlled by configuration rather than code changes.

**A senior engineer would say:** "DIP is the principle that makes all the other principles practically useful. SRP gives you small classes, OCP makes them extensible, LSP keeps substitution safe, ISP keeps interfaces focused -- but DIP is the wiring that connects everything together without creating tight coupling. Constructor injection is the single most important technique for writing testable code."

***

## Interview Insight

> [!NOTE]
>
> **DIP is the "testability" principle in interviews.** When an interviewer asks "how would you test this?" and your design has hardcoded dependencies (`self.db = MySQLDatabase()`), you are stuck -- you need a running MySQL instance for every unit test. But if you designed with DIP (`self.db = database` via constructor injection), the answer is: "I inject a MockDatabase in tests, the real database in production." This is the single most common design improvement interviewers look for. Beyond testability, DIP shows up when interviewers ask about environment parity: "how does this work in staging vs production?" If your answer is "we inject different implementations of the same interface," that is DIP in action. Key vocabulary to use: "dependency injection," "interface-based design," "loose coupling," and "constructor injection."

***

## 🏃 Next: Design Patterns

Now that you understand SOLID, let's learn the classic design patterns that solve common problems!

> **[Continue to Design Patterns →](https://resources.devweekends.com/lld/patterns/introduction)**
>
> Learn the proven solutions to recurring design problems!


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.