injection
dependency

What is dependency injection?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Dependency injection is a way of giving an object the collaborators it needs instead of letting it construct those collaborators itself. The pattern matters because it reduces coupling, makes code easier to test, and separates application wiring from application behavior.

The Problem Dependency Injection Solves

Imagine a class that creates its own database client, logger, and email sender internally. That class now knows too much about construction details.

java
1public class OrderService {
2    private final EmailSender emailSender = new SmtpEmailSender();
3
4    public void placeOrder(String orderId) {
5        emailSender.send("Order created: " + orderId);
6    }
7}

This works, but it creates tight coupling. OrderService is now hard-wired to one concrete implementation. Replacing the email sender for testing or for a different environment means editing the class itself.

Dependency injection changes that relationship.

Constructor Injection Is the Most Common Form

With constructor injection, the dependency is provided from the outside.

java
1public interface EmailSender {
2    void send(String message);
3}
4
5public class SmtpEmailSender implements EmailSender {
6    @Override
7    public void send(String message) {
8        System.out.println("Sending: " + message);
9    }
10}
11
12public class OrderService {
13    private final EmailSender emailSender;
14
15    public OrderService(EmailSender emailSender) {
16        this.emailSender = emailSender;
17    }
18
19    public void placeOrder(String orderId) {
20        emailSender.send("Order created: " + orderId);
21    }
22}

Now OrderService depends on the abstraction EmailSender, not on one hard-coded class. Something else creates the concrete object and passes it in.

This is the core idea of dependency injection.

Why This Improves Testability

The most immediate benefit shows up in tests. You can pass a fake or mock implementation instead of the real dependency.

java
1class FakeEmailSender implements EmailSender {
2    String lastMessage;
3
4    @Override
5    public void send(String message) {
6        lastMessage = message;
7    }
8}
9
10public class Demo {
11    public static void main(String[] args) {
12        FakeEmailSender fake = new FakeEmailSender();
13        OrderService service = new OrderService(fake);
14        service.placeOrder("A-42");
15        System.out.println(fake.lastMessage);
16    }
17}

That means you can test OrderService without a real SMTP server, network connection, or environment configuration.

Dependency Injection Is Not the Same as a Framework

Frameworks such as Spring, ASP.NET Core, and Angular use dependency injection heavily, but the pattern itself is not tied to any framework. You can do dependency injection manually by constructing objects in one place and passing them into other objects.

A simple manual composition root looks like this:

java
1public class Main {
2    public static void main(String[] args) {
3        EmailSender sender = new SmtpEmailSender();
4        OrderService service = new OrderService(sender);
5        service.placeOrder("A-42");
6    }
7}

The framework version automates this wiring, but the design principle is the same.

Common Forms of Injection

The most common styles are:

  • constructor injection
  • setter injection
  • method parameter injection

Constructor injection is usually preferred because it makes required dependencies obvious and ensures the object is valid from the moment it is created.

Setter injection is more appropriate for optional configuration, not for essential collaborators.

Common Pitfalls

A common mistake is thinking dependency injection means “use a container.” The container is optional. The design pattern comes first.

Another issue is injecting too many dependencies into one class. That often signals that the class has too many responsibilities.

A third problem is depending on concrete implementations everywhere. Injection is most useful when classes depend on abstractions or clearly replaceable collaborators.

Summary

  • Dependency injection means supplying dependencies from outside the class.
  • It reduces coupling between code that does work and code that creates objects.
  • Constructor injection is the clearest and most common form.
  • The pattern improves testability because fake implementations are easy to provide.
  • Frameworks automate dependency injection, but the idea works without any framework at all.

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions