Design Patterns interview question · Most asked
What is dependency injection, and what forms does it take?
Summary
Dependency injection means an object receives its collaborators from outside instead of creating them. The forms are constructor injection (the default), setter or property injection (optional or changeable dependencies) and method injection (one call needs it).
Longer Answer
DI makes dependencies explicit and swappable: tests pass fakes (an in-memory mailer, a fixed clock), and production wires real implementations in one composition root. Frameworks like Spring, Laravel's container, NestJS and FastAPI's Depends automate the wiring.
Inject things that do I/O, hold state or need faking: databases, HTTP clients, clocks, random numbers. Ten constructor parameters signal an SRP problem, not a DI problem.
Code Example
class FixedClock:
def today(self):
return "2030-01-01"
class Signup:
def __init__(self, clock): # constructor injection
self.clock = clock
def welcome(self, email):
return f"{email} joined on {self.clock.today()}"
print(Signup(FixedClock()).welcome("[email protected]"))
interface Clock { today(): string; }
class FixedClock implements Clock {
today() { return "2030-01-01"; }
}
class Signup {
constructor(private clock: Clock) {} // constructor injection
welcome(email: string) { return `${email} joined on ${this.clock.today()}`; }
}
console.log(new Signup(new FixedClock()).welcome("[email protected]"));
public class Main {
public static void main(String[] args) {
System.out.println(new Signup(new FixedClock()).welcome("[email protected]"));
}
}
interface Clock { String today(); }
class FixedClock implements Clock {
public String today() { return "2030-01-01"; }
}
class Signup {
private final Clock clock;
Signup(Clock clock) { this.clock = clock; } // constructor injection
String welcome(String email) { return email + " joined on " + clock.today(); }
}
<?php
interface Clock { public function today(): string; }
class FixedClock implements Clock {
public function today(): string { return "2030-01-01"; }
}
class Signup {
public function __construct(private Clock $clock) {} // constructor injection
public function welcome(string $email): string { return "$email joined on {$this->clock->today()}"; }
}
echo (new Signup(new FixedClock()))->welcome("[email protected]"), "\n";
Key Points
- Pass dependencies in.
- Constructor injection is the default.
- Composition root wires real objects.
- Too many dependencies = too many responsibilities.
Deep dive: Design Patterns, Explained Simply: Dependency Injection Without a Framework
Explanation
What interviewers look for
Constructor injection as default, testability benefits, and knowing DI works without a container.
Follow-up questions
- What is a composition root?
- How does Spring or Laravel resolve dependencies automatically?
More most-asked Design Patterns questions
- How do you implement a Singleton, and why is it often considered an anti-pattern?
- Explain the SOLID principles.
- What is a design pattern, and why are they useful?
- What is the difference between dependency inversion, dependency injection and inversion of control?
- Why is composition often preferred over inheritance?
- Explain the Factory Method pattern and how it differs from a simple factory.
- What problem does the Builder pattern solve? Show an example.
- What is the Adapter pattern? Give an example.