← Back to Design Principles

IoC / Dependency Injection

Design PrinciplesMid

The Question

What is Inversion of Control and Dependency Injection?

What a Strong Answer Covers

  • dependencies passed in
  • "testability

Senior-Level Answer

Inversion of Control (IoC) is a design principle where the control of object creation and dependency management is transferred from the object itself to an external entity (a framework or container). The name describes what is inverted: traditionally, a class is in control of constructing its own dependencies; with IoC, that control is inverted — the class declares what it needs, and something else provides it.

Dependency Injection (DI) is the most common technique for implementing IoC. Instead of a class instantiating its dependencies with `new`, those dependencies are "injected" (passed in) from outside — typically via constructor parameters, setter methods, or an interface.

**Without DI (tight coupling):** ```python class OrderService: def __init__(self): self.db = PostgresDatabase(host='prod-db') # hardcoded self.email = SendgridEmailClient(api_key='...') # hardcoded ```

**With DI (loose coupling):** ```python class OrderService: def __init__(self, db: Database, email: EmailClient): self.db = db self.email = email ```

Now `OrderService` depends on abstractions (`Database`, `EmailClient` interfaces), not concrete implementations. In tests, you inject mocks. In production, you inject the real implementations. The class doesn't know or care which.

**Types of injection:** - **Constructor injection** (preferred): dependencies passed in `__init__` / constructor. Makes dependencies explicit and the object always valid after construction. - **Setter injection**: dependencies set via methods after construction. Allows optional dependencies but leaves objects in a partially-initialized state. - **Interface injection**: the dependency provides an injector interface the class implements. Rare and more complex.

**DI containers** (Spring, Angular, .NET DI, Guice) automate the wiring. You register types with the container, and it resolves the full dependency graph automatically. This removes manual wiring boilerplate but adds framework coupling.

The core benefits: **testability** (inject mocks/stubs), **replaceability** (swap implementations without changing the dependent class), **separation of concerns** (construction logic moved to the composition root), and **adherence to the Dependency Inversion Principle** (depend on abstractions, not concretions — the D in SOLID).

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Correctly explains IoC as inverting control of dependency creation, defines DI as the mechanism, and shows how constructor injection enables mock substitution for testing.

3/3 — Strong Answer

Distinguishes IoC (principle) from DI (technique), covers all three injection types with trade-offs, explains DI containers vs. manual wiring, and connects to the Dependency Inversion Principle in SOLID.

Common Mistakes

  • Treating IoC and DI as synonyms — IoC is the broader principle; DI is one implementation of it.
  • Focusing only on testing benefits without mentioning replaceability and separation of concerns.
  • Not distinguishing constructor vs. setter injection and their trade-offs.
  • Describing DI containers as required — DI is achievable manually without a framework.

Follow-Up Questions

  • What is the difference between the Dependency Inversion Principle and Dependency Injection? — DIP is a design principle (depend on abstractions); DI is a technique for supplying those abstractions — DI is one way to satisfy DIP.
  • When would you prefer manual DI (poor man's DI) over a DI container? — Small applications, avoiding framework lock-in, when the container's magic makes debugging harder — trade-off of verbosity vs. automation.
  • How does DI interact with singleton vs. transient lifetimes in a DI container? — Container manages object lifetime: singleton (one instance shared), transient (new per request), scoped (one per HTTP request) — mismatching lifetimes causes bugs like captive dependencies.
  • How would you test a class that has a DI container registered in a legacy codebase without DI? — Extract dependencies as interfaces, add constructor injection alongside the default constructor, use factory methods — incremental DI adoption.

Related Questions

  • Singleton
  • Factory
  • Strategy
  • Observer
  • High Cohesion / Loose Coupling

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout