Skip to content
intermediatePhase 49 · Low-Level Design

Dependency Injection

Inject dependencies for testable, loosely-coupled code.

45m
0 problems
Topic Progress0%

DI Containers

DI containers manage object creation, lifecycle, and dependency resolution automatically.

What is DI?

Without DI (Tight Coupling):
┌──────────┐      ┌──────────────┐
│   Order   │─────▶│ MySQLDatabase │
│  Service  │      │  (concrete)  │
└──────────┘      └──────────────┘
- OrderService creates its own dependencies
- Hard to test, hard to change

With DI (Loose Coupling):
┌──────────┐      ┌────────────────┐
│   Order   │─────▶│   Database     │
│  Service  │      │  (interface)   │
└──────────┘      └───────┬────────┘
                          │ implemented by
                    ┌─────┴──────┐
                    │            │
               ┌────┴────┐ ┌────┴────┐
               │ MySQL   │ │Postgres │
               └─────────┘ └─────────┘
- OrderService receives dependency externally
- Easy to test, easy to change

DI Container Features

// Spring-style container
@Service
public class OrderService {
    private final OrderRepository repository;
    private final PaymentService paymentService;
    
    @Autowired
    public OrderService(OrderRepository repository, PaymentService paymentService) {
        this.repository = repository;
        this.paymentService = paymentService;
    }
}

// Container resolves dependencies automatically
@Service
public class OrderRepository {
    @Autowired
    private Database database;
}

// Container manages:
// 1. Object creation
// 2. Dependency resolution
// 3. Lifecycle (singleton, request, etc.)
// 4. Configuration

Container Configuration

// Java-based configuration
@Configuration
public class AppConfig {
    @Bean
    public Database database() {
        return new PostgresDatabase("jdbc:...");
    }
    
    @Bean
    @Scope("singleton")
    public OrderService orderService() {
        return new OrderService(database(), paymentService());
    }
}

// Or auto-scanning
@ComponentScan(basePackages = "com.myapp")
@Configuration
public class AppConfig { }

Common DI Containers

Container Language Features
Spring Java Full-featured, annotations
Guice Java Lightweight, module-based
Dagger Java Compile-time, no reflection
Inversify TypeScript Decorators, interfaces
NestJS TypeScript Built-in, module system
ASP.NET Core C# Built-in, constructor injection

Constructor Injection

Constructor injection provides dependencies through the constructor, ensuring immutability and clear dependencies.

Implementation

// Immutable class with constructor injection
public class OrderService {
    private final OrderRepository repository;
    private final PaymentService paymentService;
    private final NotificationService notificationService;
    
    // All dependencies injected through constructor
    public OrderService(OrderRepository repository,
                       PaymentService paymentService,
                       NotificationService notificationService) {
        this.repository = Objects.requireNonNull(repository);
        this.paymentService = Objects.requireNonNull(paymentService);
        this.notificationService = Objects.requireNonNull(notificationService);
    }
    
    public Order createOrder(CreateOrderRequest request) {
        Order order = Order.fromRequest(request);
        paymentService.charge(order);
        repository.save(order);
        notificationService.sendConfirmation(order);
        return order;
    }
}

Benefits of Constructor Injection

  1. Immutability: Dependencies can be final
  2. Clear dependencies: Constructor shows what's needed
  3. Validation: Can validate dependencies in constructor
  4. Testability: Easy to pass mocks
  5. Required dependencies: Forces all dependencies to be provided

Testing with Constructor Injection

// Easy to test!
public class OrderServiceTest {
    @Test
    void shouldCreateOrder() {
        // Create mocks
        OrderRepository mockRepo = mock(OrderRepository.class);
        PaymentService mockPayment = mock(PaymentService.class);
        NotificationService mockNotify = mock(NotificationService.class);
        
        // Inject mocks
        OrderService service = new OrderService(mockRepo, mockPayment, mockNotify);
        
        // Test in isolation
        Order order = service.createOrder(testRequest);
        
        // Verify interactions
        verify(mockRepo).save(any());
        verify(mockPayment).charge(any());
        verify(mockNotify).sendConfirmation(any());
    }
}

Constructor Injection vs Setter Injection

Aspect Constructor Setter
Required deps Optional
Immutability
Clear deps Less clear
Testing Easy Easy
Optional deps Use overloaded constructors
Circular deps Fails fast Can cause issues

Method Injection

Method injection provides dependencies through methods, useful for optional or context-dependent dependencies.

Setter Injection

public class OrderProcessor {
    private OrderRepository repository;
    private PaymentService paymentService;
    
    // Setter injection for optional dependencies
    public void setRepository(OrderRepository repository) {
        this.repository = repository;
    }
    
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    // Dependencies can be null if not set
    public void process(Order order) {
        if (repository != null) {
            repository.save(order);
        }
    }
}

Method Injection

public class ReportGenerator {
    // Method injection: dependency provided per method call
    public Report generate(DataProvider provider) {
        Data data = provider.getData();
        return buildReport(data);
    }
}

// Different providers for different contexts
Report dailyReport = generator.generate(new DailyDataProvider());
Report monthlyReport = generator.generate(new MonthlyDataProvider());

Field Injection (Anti-Pattern)

@Service
public class OrderService {
    @Autowired
    private OrderRepository repository;  // Anti-pattern!
    
    // Problems:
    // - Can't make final
    // - Can't validate in constructor
    // - Hard to test without container
    // - Hidden dependencies
}

Injection Methods Comparison

Method Immutability Testing Required Recommended
Constructor Easy ✓ Best
Setter Easy Optional Sometimes
Method Easy Per-call Rare
Field Hard ✗ Avoid

Best Practices

  1. Prefer constructor injection for required dependencies
  2. Use setter injection for optional dependencies
  3. Avoid field injection — hidden dependencies, hard to test
  4. Validate dependencies in constructor (null checks)
  5. Use interfaces for dependencies (DIP)
  6. Keep constructors small — few dependencies means good design

Practice Problems

0/3solved
Design Dependency Injection System

Design a scalable Dependency Injection system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Dependency Injection Scaling

How would you scale Dependency Injection to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Dependency Injection Failure Modes

Analyze potential failure modes for Dependency Injection and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What is dependency injection?

Question 1 options

2. Which injection method is recommended?

Question 2 options

3. Why is field injection an anti-pattern?

Question 3 options

4. What does a DI container manage?

Question 4 options

5. Why use interfaces for injected dependencies?

Question 5 options

Flashcards

Question

What is dependency injection?

Answer

Providing dependencies from outside rather than creating them internally. Enables loose coupling, testability, and flexibility.

Question

Best injection method?

Answer

Constructor injection. Enables immutability, clear dependencies, validation, and easy testing. Use setter for optional deps.

Question

Why avoid field injection?

Answer

Hidden dependencies, can't make final, can't validate in constructor, hard to test without container.

Question

What does a DI container do?

Answer

Manages object creation, resolves dependencies automatically, controls lifecycle (singleton, request), handles configuration.

Question

DI and SOLID principles?

Answer

DI implements DIP (depend on abstractions). Enables SRP (separate concerns). Supports OCP (easy to extend).

Revision Notes

Key Takeaways

  • 1.DI provides dependencies from outside, enabling loose coupling
  • 2.Constructor injection is recommended for required dependencies
  • 3.DI containers manage object creation and dependency resolution
  • 4.Field injection is an anti-pattern — hidden dependencies, hard to test
  • 5.DI implements Dependency Inversion Principle (DIP) from SOLID

Interview Tips

  • Explain how DI enables loose coupling and testability
  • Show constructor injection in your design
  • Discuss how DI container simplifies dependency management
  • Explain why field injection should be avoided

Cheat Sheet

Dependency Injection - Cheat Sheet

DI = Dependencies from outside, not created internally.

Injection Methods:

Method Immutability Testing Recommended
Constructor Easy ✓ Best
Setter Easy Optional
Method Easy Rare
Field Hard ✗ Avoid

DI Container Features:

  • Auto object creation
  • Dependency resolution
  • Lifecycle management
  • Configuration

Common Containers:
Spring, Guice, Dagger, NestJS, ASP.NET

Best Practices:

  1. Constructor injection for required deps
  2. Setter for optional
  3. Avoid field injection
  4. Use interfaces (DIP)
  5. Validate in constructor