Medium15 minDesign Patterns
UpdatedAug 1, 2026
Edit

Dependency Injection

CONCEPTS:Dependency Injection (DI)

Question Variations

  • "What is Dependency Injection, and how does it relate to Inversion of Control (IoC)?"
  • "Explain the difference between Transient, Scoped, and Singleton lifetimes in a DI container."
  • "Why is constructor injection generally preferred over property or method injection?"
  • "How does Dependency Injection improve the testability of your code?"

Why This Is Asked

DI is a foundational pattern in modern software. Interviewers want to confirm you understand Inversion of Control, can explain the benefits of loose coupling and testability, and know how DI containers manage object lifetimes.

Key Concepts

  • Dependency Injection is a form of Inversion of Control (IoC) where dependencies are provided externally
  • Three injection styles: constructor injection (most common), method injection, property injection
  • Service lifetimes: Transient (new instance per request), Scoped (per scope/HTTP request), Singleton (shared)
  • Enables unit testing by allowing mock/stub substitution
  • The composition root is where the entire object graph is wired up

Question Variations

  • “What is Dependency Injection, and how does it relate to Inversion of Control (IoC)?”
  • “Explain the difference between Transient, Scoped, and Singleton lifetimes in a DI container.”
  • “Why is constructor injection generally preferred over property or method injection?”
  • “How does Dependency Injection improve the testability of your code?”

Answers by Technology

+ Add Variant

Expected Answer (.NET 10 / C# 14)

ASP.NET Core provides a built-in dependency injection (DI) container that manages the lifetime and resolution of services. The three primary service lifetimes are:

  1. Transient: Services are created every time they are requested from the service container. This lifetime works best for lightweight, stateless services.
  2. Scoped: Services are created once per client request (connection). All components processing the same web request share the same instance.
  3. Singleton: Services are created the first time they are requested (or when Startup runs) and every subsequent request uses the same instance.

Why It Matters

Proper DI management is crucial for loose coupling and testability. It allows you to swap implementations (e.g., using a Mock repository in tests) without changing the consuming class. Misunderstanding lifetimes—especially “Captive Dependencies” (injecting a Scoped service into a Singleton)—can lead to memory leaks, stale data, and concurrency bugs.

Example Code

// Registration in Program.cs
builder.Services.AddTransient<IMyTransientService, MyTransientService>();
builder.Services.AddScoped<IMyScopedService, MyScopedService>();
builder.Services.AddSingleton<IMySingletonService, MySingletonService>();

// Usage in a Controller
public class MyController : ControllerBase
{
    private readonly IMyScopedService _scopedService;

    public MyController(IMyScopedService scopedService)
    {
        _scopedService = scopedService;
    }
}

Common Mistakes

  • Captive Dependency: Injecting a Scoped service into a Singleton. The Scoped service will effectively become a Singleton, which might break its intended behavior (e.g., sharing a database context across multiple requests).
  • Service Locator Anti-pattern: Manually resolving services from IServiceProvider inside your business logic instead of using constructor injection. This makes the code harder to test and hides dependencies.

Follow-up Questions

  • How do you resolve a service manually when constructor injection isn’t available? (Answer: Use IServiceScopeFactory to create a new scope and then resolve the service from the scope’s IServiceProvider).
  • What is the difference between AddSingleton<TService, TImplementation>() and AddSingleton(new TImplementation())? (Answer: The first is lazily created on first request; the second is eagerly created during registration).

References

Expected Answer (Java 26)

In the Java ecosystem, Dependency Injection is primarily associated with Spring Framework and Jakarta EE (CDI).

  1. Spring Context: The IoC container manages beans and their dependencies.
  2. Annotation-Driven: Uses @Autowired (Spring) or @Inject (Jakarta EE) to resolve dependencies.
  3. Service Lifetimes (Scopes):
    • Singleton: One instance per Spring container. The same instance is injected into all dependent beans. (Default).
    • Prototype: A new instance is created every time the bean is requested from the container.
    • Request: A new instance is created for each HTTP request (only valid in web-aware Spring contexts). Similar to .NET’s “Scoped”.
    • Session: A new instance is created for each HTTP session.

Why It Matters

Java enterprise applications rely heavily on DI to manage complexity. Spring’s implementation of IoC allows for decoupled architectures where infrastructure (like DB transactions) can be injected via AOP (Aspect Oriented Programming) without polluting business logic.

Code Example

// Spring Framework Constructor Injection
@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired // Optional in modern Spring for constructor injection
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Common Mistakes

  • Field Injection: Using @Autowired on private fields makes unit testing difficult without a Spring context. Constructor injection is the recommended best practice.
  • Circular Dependencies: Spring will throw a BeanCurrentlyInCreationException if two beans depend on each other via constructors.

Follow-up Questions

  • Constructor vs Field Injection? (Answer: Constructor injection ensures required dependencies are present and allows for final fields).
  • What is a BeanPostProcessor? (Answer: An interface that allows for custom modification of new bean instances).

Expected Answer (PHP 8.5)

In modern PHP, Dependency Injection is handled by a Service Container (IoC container).

  1. Constructor Injection: The preferred method. The container uses reflection to automatically instantiate and inject dependencies.
  2. Laravel Service Container: Allows binding interfaces to concrete implementations via Service Providers.
  3. Symfony DependencyInjection Component: Uses YAML, XML, or PHP configuration to define how services are instantiated.
  4. Service Lifetimes:
    • Singleton: The container resolves the dependency once and returns the same instance for the rest of the request cycle.
    • Transient (Binding): A new instance is created every time it is resolved.
    • Scoped: In PHP (Shared Nothing), most services are effectively scoped to the single HTTP request by default.

Why It Matters

PHP started as a script-based language but evolved into a robust enterprise language. DI is critical for decoupling web applications, allowing for easier testing (e.g., swapping a real mailer for a MockMailer) and managing the “shared nothing” architecture of PHP.

Code Example

// Laravel Constructor Injection
namespace App\Services;

use App\Repositories\UserRepository;

class UserService
{
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
}

Common Mistakes

  • Using the Service Locator pattern: Calling the container directly (e.g., app('service') or $container->get('service')) inside a class. This hides dependencies and makes testing harder.
  • Static methods for business logic: Relying on static methods instead of injected services prevents mocking and leads to tight coupling.

Follow-up Questions

  • Binding Interfaces: How do you tell the container which implementation to use for an interface? (Answer: In Laravel, using $this->app->bind(Interface::class, Implementation::class) in a Service Provider).
  • Singleton vs Transient: How are they handled in PHP? (Answer: Most services are singletons within the scope of a single request).