---
# === IDENTITY ===
id: software/patterns/dependency-injection/2026
canonical_question: "What are the dependency injection patterns across languages?"
aliases:
  - "DI patterns comparison"
  - "constructor injection vs setter injection"
  - "dependency injection types explained"
  - "how to implement dependency injection"
  - "DI container vs manual injection"
  - "inversion of control dependency injection"
  - "dependency injection best practices"
  - "when to use dependency injection"
entity_type: software_reference
domain: software > patterns > dependency_injection
region: global
jurisdiction: global
temporal_scope: 2004-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.92
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: null
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Constructor injection should be the default — use setter/property injection only for optional dependencies"
  - "Never inject concrete classes directly — always inject interfaces or abstract types to preserve testability"
  - "Avoid circular dependencies — they indicate a design flaw and most DI containers will throw at startup"
  - "Do not use the DI container as a service locator — resolving dependencies manually inside business logic defeats the purpose of DI"
  - "In Go, prefer manual DI (constructor functions) over reflection-based containers unless the project has 50+ services"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Looking for a specific DI framework's API reference (NestJS, Spring, .NET)"
    use_instead: "Consult the framework's official documentation"
  - condition: "Debugging a NullPointerException caused by failed injection in Spring Boot"
    use_instead: "software/debugging/spring-boot-npe/2026"
  - condition: "Designing a plugin/extension system rather than wiring application services"
    use_instead: "software/patterns/plugin-architecture/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: language
    question: "Which programming language or framework are you using?"
    type: choice
    options: ["TypeScript/NestJS", "Python", "Java/Spring", "Go", "C#/.NET", "Other"]
  - key: project_size
    question: "How large is the project (number of services/classes wired together)?"
    type: choice
    options: ["Small (< 10 services)", "Medium (10-50 services)", "Large (50+ services)"]
  - key: di_preference
    question: "Do you prefer a DI container/framework or manual wiring?"
    type: choice
    options: ["Container/framework", "Manual/pure DI", "Not sure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/patterns/dependency-injection/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: software/patterns/repository-pattern/2026
      label: "Repository Pattern"
    - id: software/patterns/middleware-pipeline/2026
      label: "Middleware Pipeline Pattern"
    - id: software/patterns/state-machine-implementation/2026
      label: "State Machine Implementation"
  solves: []
  alternative_to:
    - id: software/patterns/service-locator/2026
      label: "Service Locator Pattern"
  often_confused_with:
    - id: software/patterns/inversion-of-control/2026
      label: "Inversion of Control (broader principle)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Inversion of Control Containers and the Dependency Injection pattern"
    author: Martin Fowler
    url: https://martinfowler.com/articles/injection.html
    type: technical_blog
    published: 2004-01-23
    reliability: authoritative
  - id: src2
    title: "Dependency Injection :: Spring Framework Reference"
    author: Spring
    url: https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html
    type: official_docs
    published: 2025-11-20
    reliability: high
  - id: src3
    title: "Service Locator is an Anti-Pattern"
    author: Mark Seemann
    url: https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
    type: technical_blog
    published: 2010-02-03
    reliability: high
  - id: src4
    title: "A Guide on Dependency Injection in NestJS"
    author: DigitalOcean
    url: https://www.digitalocean.com/community/tutorials/a-guide-on-dependency-injection-in-nestjs
    type: community_resource
    published: 2024-03-15
    reliability: moderate_high
  - id: src5
    title: "Go Dependency Injection Approaches — Wire vs. fx, and Manual Best Practices"
    author: Leapcell
    url: https://leapcell.io/blog/go-dependency-injection-approaches-wire-vs-fx-and-manual-best-practices
    type: technical_blog
    published: 2025-06-10
    reliability: moderate_high
  - id: src6
    title: "Dependency Injection in Go: Comparing Wire, Dig, Fx & More"
    author: DEV Community
    url: https://dev.to/rezende79/dependency-injection-in-go-comparing-wire-dig-fx-more-3nkj
    type: community_resource
    published: 2025-04-22
    reliability: moderate_high
  - id: src7
    title: "Dependency Injection — Design Patterns Explained"
    author: Stackify
    url: https://stackify.com/dependency-injection/
    type: technical_blog
    published: 2024-09-12
    reliability: moderate_high
---

# Dependency Injection Patterns Across Languages

## TL;DR

- **Bottom line**: Dependency injection (DI) externalizes object creation so components receive their dependencies rather than creating them, enabling loose coupling, testability, and swappable implementations across all major languages.
- **Key tool/command**: Constructor injection via interfaces — the universal default pattern regardless of language or framework.
- **Watch out for**: Using the DI container as a service locator inside business logic, which hides dependencies and defeats the purpose of DI.
- **Works with**: Every object-oriented and most functional languages — TypeScript, Python, Java, Go, C#, Kotlin, Rust, Ruby, PHP.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Constructor injection should be the default — use setter/property injection only for optional dependencies
- Never inject concrete classes directly — always inject interfaces or abstract types to preserve testability
- Avoid circular dependencies — they indicate a design flaw and most DI containers will throw at startup
- Do not use the DI container as a service locator — resolving dependencies manually inside business logic defeats the purpose of DI [src3]
- In Go, prefer manual DI (constructor functions) over reflection-based containers unless the project has 50+ services [src5]

## Quick Reference

| DI Type | Mechanism | Testability | Complexity | Best For | Languages/Frameworks |
|---|---|---|---|---|---|
| Constructor Injection | Dependencies passed via constructor parameters | Excellent — all deps visible in signature | Low | Required dependencies, immutable objects | All (universal) |
| Property/Setter Injection | Dependencies assigned via public setters after construction | Good — but allows partial initialization | Low | Optional dependencies, circular dep workarounds | Java/Spring, C#/.NET, Python |
| Method Injection | Dependencies passed as method parameters per-call | Excellent — scoped to single call | Low | Per-request/transient dependencies | All (universal) |
| Interface Injection | Component implements an injection interface the container calls | Good — but verbose | Medium | Plugin architectures, legacy systems | Java (Avalon), less common today |
| Container-Managed (Framework DI) | Framework auto-resolves via decorators, annotations, or config | Excellent — with framework test utils | Medium-High | Large apps with many services (50+) | Spring, NestJS, .NET, Angular, Fx |
| Manual/Pure DI | Wiring done explicitly in composition root, no container | Excellent — fully transparent | Low-Medium | Small-medium apps, Go idiom | Go, any language |

## Decision Tree

```
START
├── Is the dependency required for the object to function?
│   ├── YES → Use Constructor Injection
│   └── NO → Is the dependency optional or has a sensible default?
│       ├── YES → Use Property/Setter Injection
│       └── NO ↓
├── Does the dependency vary per method call (e.g., per-request context)?
│   ├── YES → Use Method Injection
│   └── NO ↓
├── How many services need wiring (project size)?
│   ├── < 20 services → Manual/Pure DI in a composition root
│   ├── 20-50 services → Consider lightweight container (Wire for Go, simple factory for TS/Python)
│   └── > 50 services → Use a full DI framework (Spring, NestJS, .NET DI, Uber Fx)
│       └── NO ↓
└── DEFAULT → Constructor Injection with manual wiring; add a container when boilerplate becomes unmanageable
```

## Step-by-Step Guide

### 1. Define abstractions (interfaces)

Identify the boundaries between components. Each dependency should be represented by an interface, not a concrete class. This enables swapping implementations for testing and different environments. [src1]

```typescript
// TypeScript — define an interface for the dependency
interface Logger {
  log(message: string): void;
}

interface UserRepository {
  findById(id: string): Promise<User | null>;
}
```

**Verify**: Every class depends only on interfaces, never on concrete implementations in its constructor signature.

### 2. Implement constructor injection

Pass all required dependencies through the constructor. Store them as private readonly fields. [src7]

```typescript
class UserService {
  constructor(
    private readonly userRepo: UserRepository,
    private readonly logger: Logger,
  ) {}

  async getUser(id: string): Promise<User | null> {
    this.logger.log(`Fetching user ${id}`);
    return this.userRepo.findById(id);
  }
}
```

**Verify**: The class has no `new` keywords for its dependencies — all are received from outside.

### 3. Create a composition root

Wire all dependencies in one place — the application entry point. This is where concrete implementations are chosen and connected. [src1]

```typescript
// main.ts — composition root
const logger = new ConsoleLogger();
const userRepo = new PostgresUserRepository(dbPool);
const userService = new UserService(userRepo, logger);

// Start the application with fully wired dependencies
const app = createApp(userService);
app.listen(3000);
```

**Verify**: Only the composition root references concrete classes. All other modules depend on interfaces.

### 4. Write tests with mock dependencies

Replace real implementations with test doubles. Constructor injection makes this trivial. [src7]

```typescript
// user-service.test.ts
const mockRepo: UserRepository = {
  findById: jest.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
};
const mockLogger: Logger = { log: jest.fn() };

const service = new UserService(mockRepo, mockLogger);
const user = await service.getUser('1');
expect(user?.name).toBe('Alice');
```

**Verify**: `npm test` passes with no real database or external service connections.

### 5. Migrate to a DI container (optional, for larger projects)

When manual wiring becomes too verbose, introduce a framework container. Register providers and let the container resolve the dependency graph. [src4]

```typescript
// NestJS example — the framework handles wiring
@Injectable()
class UserService {
  constructor(
    @Inject('USER_REPO') private readonly userRepo: UserRepository,
    private readonly logger: LoggerService,
  ) {}
}
```

**Verify**: Application starts without `NullInjectorError` or `Cannot resolve dependency` errors.

## Code Examples

### TypeScript/NestJS: Container-Managed DI

> Full script: [typescript-nestjs-container-managed-di.ts](scripts/typescript-nestjs-container-managed-di.ts) (27 lines)

```typescript
// Input:  NestJS module with provider registration
// Output: Auto-wired UserService with injected dependencies
import { Module, Injectable, Inject } from '@nestjs/common';
interface UserRepository {
  findById(id: string): Promise<User | null>;
# ... (see full script)
```

### Python: Manual Constructor Injection

```python
# Input:  Protocol-based interfaces, manual wiring
# Output: Testable service with swappable dependencies

from typing import Protocol

class UserRepository(Protocol):
    def find_by_id(self, user_id: str) -> dict | None: ...

class PostgresUserRepository:
    def __init__(self, dsn: str) -> None:
        self.dsn = dsn

    def find_by_id(self, user_id: str) -> dict | None:
        # real database query
        return {"id": user_id, "name": "Alice"}

class UserService:
    def __init__(self, repo: UserRepository) -> None:
        self._repo = repo  # injected, not created here

    def get_user(self, user_id: str) -> dict | None:
        return self._repo.find_by_id(user_id)

# Composition root
repo = PostgresUserRepository(dsn="postgresql://localhost/mydb")
service = UserService(repo=repo)
```

### Java/Spring: Annotation-Based DI

> Full script: [java-spring-annotation-based-di.java](scripts/java-spring-annotation-based-di.java) (25 lines)

```java
// Input:  Spring Boot application with @Service and @Repository
// Output: Auto-wired beans managed by Spring container
public interface UserRepository {
    Optional<User> findById(String id);
}
# ... (see full script)
```

### Go: Manual DI (Idiomatic)

```go
// Input:  Interface-based dependencies, constructor functions
// Output: Testable service with explicit wiring

package user

import "context"

// Interface (implicit in Go — no "implements" keyword)
type Repository interface {
    FindByID(ctx context.Context, id string) (*User, error)
}

// Service receives its dependency via constructor function
type Service struct {
    repo Repository
}

func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
    return s.repo.FindByID(ctx, id)
}

// main.go — composition root
// repo := postgres.NewRepository(db)
// svc := user.NewService(repo)
```

### Go: Container-Managed DI with Uber Fx

```go
// Input:  Uber Fx module with provider functions
// Output: Auto-wired application with lifecycle management

package main

import (
    "go.uber.org/fx"
    "myapp/user"
    "myapp/postgres"
)

func main() {
    fx.New(
        fx.Provide(
            postgres.NewRepository, // provides user.Repository
            user.NewService,        // consumes user.Repository
            NewHTTPHandler,         // consumes *user.Service
        ),
        fx.Invoke(StartServer),
    ).Run()
}
```

## Anti-Patterns

### Wrong: Service Locator inside business logic

```typescript
// BAD — hides dependencies, untestable, couples to container
class UserService {
  getUser(id: string) {
    const repo = Container.resolve<UserRepository>('UserRepo');
    return repo.findById(id);
  }
}
```

### Correct: Constructor injection with explicit dependencies

```typescript
// GOOD — dependencies are visible, testable, swappable
class UserService {
  constructor(private readonly repo: UserRepository) {}

  getUser(id: string) {
    return this.repo.findById(id);
  }
}
```

### Wrong: God container with everything registered

```typescript
// BAD — single container with 200+ registrations, no module boundaries
const container = new Container();
container.register('db', PostgresDB);
container.register('cache', RedisCache);
container.register('logger', WinstonLogger);
container.register('userRepo', UserRepo);
container.register('orderRepo', OrderRepo);
// ... 195 more registrations in one file
```

### Correct: Modular registration with scoped containers

```typescript
// GOOD — each module registers its own providers
@Module({
  providers: [UserRepository, UserService],
  exports: [UserService],
})
export class UserModule {}

@Module({
  providers: [OrderRepository, OrderService],
  imports: [UserModule],  // explicit dependency between modules
})
export class OrderModule {}
```

### Wrong: Injecting too many dependencies (constructor over-injection)

```java
// BAD — 8+ constructor params = class does too much (SRP violation)
public class OrderService {
  public OrderService(
    UserRepo userRepo, OrderRepo orderRepo, PaymentGateway payment,
    EmailService email, Logger logger, MetricsService metrics,
    CacheService cache, AuditService audit, NotificationService notifs
  ) { /* ... */ }
}
```

### Correct: Refactor into smaller, focused services

```java
// GOOD — split into focused services with 2-4 dependencies each
public class OrderService {
  public OrderService(OrderRepo orderRepo, PaymentGateway payment,
                      OrderNotifier notifier) { /* ... */ }
}

// OrderNotifier encapsulates email + push + audit concerns
public class OrderNotifier {
  public OrderNotifier(EmailService email, NotificationService notifs,
                       AuditService audit) { /* ... */ }
}
```

### Wrong: Circular dependencies

```typescript
// BAD — A depends on B, B depends on A → container crashes
@Injectable()
class AuthService {
  constructor(private userService: UserService) {}
}

@Injectable()
class UserService {
  constructor(private authService: AuthService) {}
}
```

### Correct: Break the cycle with an intermediate abstraction or events

```typescript
// GOOD — extract shared logic or use events
@Injectable()
class AuthService {
  constructor(private tokenValidator: TokenValidator) {}
}

@Injectable()
class UserService {
  constructor(private tokenValidator: TokenValidator) {}
}

// TokenValidator has no dependency on either service
@Injectable()
class TokenValidator {
  validate(token: string): boolean { /* ... */ }
}
```

## Common Pitfalls

- **Creating dependencies with `new` inside classes**: Bypasses DI entirely, making the class untestable. Fix: move `new` calls to the composition root or container registration. [src1]
- **Using `@Autowired` on fields instead of constructors (Java/Spring)**: Fields can be null during construction and break immutability. Fix: use constructor injection — Spring auto-detects single constructors since 4.3. [src2]
- **Registering everything as singleton when it should be transient**: Shared mutable state causes race conditions. Fix: match the lifecycle scope (singleton, scoped, transient) to the dependency's statefulness. [src7]
- **Forgetting to register a provider**: Results in cryptic runtime errors like `No provider for X`. Fix: ensure every interface has a registered implementation in the module/container. [src4]
- **Over-engineering small projects with DI frameworks**: Adds complexity without benefit for < 10 services. Fix: start with manual constructor injection; add a container when boilerplate becomes painful. [src5]
- **Injecting the container itself**: Makes every class depend on the entire application. Fix: inject only the specific interfaces each class needs. [src3]
- **Ignoring Go's implicit interfaces**: Trying to force Java-style DI containers in Go. Fix: use constructor functions returning interfaces — Go's structural typing provides DI naturally. [src6]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building applications with multiple collaborating services that need testing | Writing a simple script or CLI tool with < 5 functions | Direct instantiation |
| You need to swap implementations (prod database vs test mock) | The project is a prototype/throwaway code with no tests | Simplest possible approach |
| Working with a framework that provides DI (Spring, NestJS, .NET) | Adding DI would require more code than the entire application | Plain function composition |
| Multiple teams work on different modules that need clear boundaries | All dependencies are pure functions with no side effects | Functional composition / higher-order functions |
| You need lifecycle management (startup/shutdown ordering) | Performance-critical hot paths where allocation overhead matters | Object pools or static dispatch |

## Important Caveats

- DI containers add a layer of indirection that can make debugging harder — stack traces go through container internals rather than direct calls. Use manual DI in performance-critical paths.
- Reflection-based containers (Spring, Uber Dig/Fx) have startup cost proportional to the number of registered types. Compile-time containers (Wire, Dagger 2) avoid this but require a build step.
- The "best" DI pattern depends on language idiom: Java/C# favor containers, Go favors manual injection, Python uses both (with `dependency-injector` or manual Protocol-based DI). Do not transplant idioms across languages blindly.
- Constructor injection with 4+ parameters is a code smell indicating the class has too many responsibilities — refactor before adding more dependencies. [src1]
- In dynamically-typed languages (Python, Ruby), DI is simpler because duck typing provides implicit interfaces, but you lose compile-time verification of dependency contracts.

## Related Units

- [Repository Pattern](/software/patterns/repository-pattern/2026)
- [Middleware Pipeline Pattern](/software/patterns/middleware-pipeline/2026)
- [State Machine Implementation](/software/patterns/state-machine-implementation/2026)
