skip to content

How do the Dependency Inversion Principle, Inversion of Control, and Dependency Injection differ? Can you have one without the others?

level: middleimportance: must knowfreq 71%

answer

  1. principle → style → technique
  2. IoC ⊃ DI; DIP is orthogonal
  3. inject a concrete class = DI without DIP
  4. hand-wired main = DIP without a container
  5. Hollywood: don't call us, we'll call you

basics

~20 s

DIP is a design goal about which way source dependencies point. Inversion of Control is a broader style where an outside caller or framework drives your code. Dependency Injection is a concrete technique: hand an object its collaborators from outside instead of it creating them.

solid answer

~50 s

Think principle → style → technique. **DIP** is a principle about the *shape of the dependency graph*: policy and details both depend on abstractions the policy owns. **IoC** is a broad style in which *who is in charge* is inverted — a framework, container, or outer layer decides when your code runs (event loops, template methods, callbacks, lifecycle hooks). **DI** is one specific IoC technique: an object's collaborators are supplied by a caller (constructor, setter, or method parameter) rather than constructed internally, typically assembled in a composition root or a container. They are independent axes. You can use DI and still violate DIP by injecting a *concrete* class — no abstraction, no inversion of the source dependency. You can satisfy DIP with no container at all by hand-wiring in `main`. And a framework can exercise IoC over you (it calls your handler) without any DI involved. Interviewers use this question to see whether you can separate goals from tooling.

go deeper

for a junior

Say DIP = depend on abstractions, DI = collaborators passed in from outside, IoC = the framework calls your code. One example each.

for a middle

Show the containment relation (DI ⊂ IoC) and give the DI-without-DIP counterexample of injecting a concrete class.

for a senior

Discuss composition root, injection styles and their trade-offs, container vs pure DI, and why a container alone doesn't invert anything.

for a principal

Talk about the dependency graph as an architectural asset, enforcing import direction in the build, lifetime/scoping hazards, and choosing not to impose containers on libraries.

## Three ideas at three altitudes ### 1. Dependency Inversion Principle — a *principle* (the goal) A statement about the direction of source-code dependencies: high-level policy and low-level details should both depend on abstractions, and abstractions must not be shaped by details. Success criterion: the policy module's import list contains no infrastructure. ### 2. Inversion of Control — a *style* (who is in charge) Martin Fowler's framing: in a library, *you* call *it*; in a framework, *it* calls *you*. IoC is that reversal of who owns the main loop. It covers far more than dependency wiring: - **Template Method**: the base class runs the algorithm and calls your overridden steps. - **Event handlers / callbacks / observers**: the runtime invokes your function when something happens. - **HTTP frameworks**: routing machinery invokes your controller. - **Lifecycle hooks / schedulers / test runners**: they own `main`, you supply fragments. - **Dependency Injection**: control over *object construction and wiring* is taken from the object and given to an outside assembler. So DI is a *subset* of IoC — specifically, inversion of control over *dependency acquisition*. ### 3. Dependency Injection — a *technique* (the mechanism) Instead of `val mailer = SmtpMailer()` inside the class, the collaborator arrives from outside: - **Constructor injection** — required collaborators; object is never in an invalid state; the constructor signature honestly advertises what the class needs. Default choice. - **Setter/property injection** — optional or reconfigurable collaborators; risks temporarily invalid objects. - **Method injection** — passed per call when it varies per invocation. - **Interface injection** — the dependency is supplied via a setter declared on an interface; rare. A **DI container / IoC container** (Spring, Guice, Dagger, .NET's built-in provider, Symfony DI…) is *optional automation* over DI: it reads registrations or annotations and builds the object graph for you, managing lifetimes (singleton, per-request, transient). Without one you use **Pure DI**: hand-wire the graph in a single composition root at the entry point. Pure DI is compile-time-checked and perfectly legitimate; containers pay off when the graph is large or lifetimes are complex. ## The independence, concretely | Situation | DI? | DIP? | Why | |---|---|---|---| | `class Service(val db: PostgresRepo)` — injected concrete class | yes | **no** | The source dependency still points from policy to the detail; you can't substitute anything. | | `class Service(val repo: OrderRepo)` where `OrderRepo` is declared in the policy package | yes | yes | Injection *plus* an owned abstraction. | | `class Service { val repo = OrderRepoImpl() }` where the field's *type* is an interface but it constructs the impl | no | **no** | Naming the concrete type re-creates the dependency; a factory owned by policy would be needed. | | Framework calls your `onMessage(evt)` handler; you `new` everything inside | IoC yes, DI no | probably no | Control is inverted, dependencies are not. | | Hand-wired `main()` builds `Service(SmtpNotifier())` with `Notifier` owned by policy | DI yes (pure), container no | yes | No container required for either IoC-of-wiring or DIP. | | **Service Locator**: `val repo = Locator.get<OrderRepo>()` | not DI | weakened | The dependency is hidden and the class now depends on the locator; requirements are invisible to callers and tests. Widely considered an anti-pattern relative to DI. | ## Common interview traps - "DIP and DI are the same thing" — no; one is about arrow direction, the other about how an object obtains a collaborator. - "You need Spring/Guice to do DIP" — no; a constructor parameter is enough. - "IoC container" and "IoC" are the same — no; the container is a tool, IoC is a much older and broader idea (the Hollywood Principle: "don't call us, we'll call you"). - Injecting a concrete class and claiming decoupling. ## Why the distinction matters in practice If you conflate them you optimize the wrong thing: teams add a container, annotate everything, and still have business code importing the ORM, HTTP client, and cloud SDK. The container gave them wiring convenience but the dependency graph — the thing that determines build times, test speed, and change ripple — never inverted.

  • Give a case that is DI but not DIP.
    Constructor-injecting a concrete `PostgresOrderRepository` into a service. The dependency is injected — testability is slightly better in that you can subclass it — but the service's source code still names the database class, so nothing is substitutable and the arrow still points from policy to detail.
  • Why is a Service Locator usually considered worse than injection?
    It hides requirements: the constructor no longer tells you what the class needs, so misconfiguration fails at runtime rather than at construction; every class gains a dependency on the locator itself; and tests must mutate global state, which harms isolation and parallelism.
  • When is a DI container not worth it?
    Small graphs, libraries (never impose a container on consumers), and contexts where compile-time verification and startup speed matter. Pure DI in a composition root is explicit, debuggable, and checked by the compiler; containers earn their place with large graphs, scoped lifetimes, and cross-cutting concerns.

Hiring a contractor. DIP is the rule that you write the job spec (the abstraction) and any qualified contractor must satisfy it. IoC is the arrangement where the site manager tells workers when to show up rather than each worker deciding. DI is the logistics of handing a worker their tools at the door instead of letting them bring their own.

saying these in an interview costs you the question

  • "DIP, IoC and DI are three names for the same thing."
  • "You need a DI framework to follow DIP."
  • Calling any use of a container automatically good design regardless of what is injected.
  • Treating Service Locator as a form of dependency injection.
  • Thinking IoC only refers to dependency wiring, ignoring template methods, callbacks and framework lifecycles.

context