Dependency Injection Vocabulary: IoC, DI Containers, and SOLID Principles

Dependency injection, inversion of control, DI containers, constructor injection, and SOLID vocabulary for backend developers.

If you work on any modern backend codebase — whether in Java, C#, Python, or TypeScript — you will encounter the vocabulary of dependency injection almost daily. Stand-ups, code reviews, architecture discussions, and pull request comments all assume you know what it means to “wire up a container,” “register a service,” or “violate the dependency inversion principle.” This guide covers every essential term, with plain-English definitions and real conversational examples so you can follow and join those discussions confidently.


Core Terms

Dependency injection (DI) — a design pattern in which a class receives the objects it needs (its dependencies) from an external source rather than creating them itself. Instead of writing new EmailService() inside your class, the email service is handed in from outside.

“We refactored the payment module to use DI — now the controller doesn’t instantiate anything directly.”

“Before we added DI, every class was newing up its own database connection. Testing was a nightmare.”

Inversion of control (IoC) — the broader principle that says a framework or container, not your own code, should control the flow of the application and create objects. Dependency injection is one way to achieve IoC.

“The whole point of IoC is that your business logic doesn’t care how it gets its dependencies — it just declares what it needs.”

“We moved to an IoC container and suddenly the startup code shrank from 300 lines to about 40.”

DI container (also called an IoC container) — a framework component that automatically creates objects, resolves their dependencies, and manages their lifetimes. Examples include Spring (Java), .NET’s built-in IServiceCollection, and InversifyJS (TypeScript).

“All our service registrations live in one place — the container handles everything from there.”

“If the container can’t resolve a dependency at startup, it throws immediately rather than failing silently at runtime.”

SOLID — an acronym for five object-oriented design principles: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. The last two are especially relevant to DI.

“The team did a SOLID review of the new module and found three classes that were doing way too much.”

“Understanding SOLID is table stakes for senior backend roles — interviewers ask about it constantly.”


Injection Styles

Constructor injection — the most common and recommended style, where dependencies are passed in through a class’s constructor. The class cannot be instantiated without providing its dependencies, making them explicit and mandatory.

“We standardised on constructor injection across the whole service — if you need something, declare it in the constructor.”

“Constructor injection makes it obvious what a class depends on; you don’t have to read every method to find hidden dependencies.”

Setter injection — dependencies are provided through public setter methods after the object is constructed. Useful when a dependency is optional or when there is a circular dependency that cannot be resolved at construction time.

“We used setter injection for the optional audit logger — if it’s not configured, the service still works fine.”

“Setter injection can be risky because the object exists in a partially initialised state between construction and injection.”

Property injection — similar to setter injection but dependencies are assigned directly to public properties rather than through dedicated setter methods. Common in some frameworks but generally considered less safe than constructor injection.

“The legacy code used property injection everywhere, which made it really hard to see what was actually required.”

“Our style guide discourages property injection — if it’s optional, use a setter; if it’s required, use the constructor.”


Design Principles and Patterns

Dependency inversion principle — the “D” in SOLID. It states that high-level modules should not depend on low-level modules; both should depend on abstractions (interfaces). Your OrderService should depend on an IPaymentGateway interface, not on a concrete StripePaymentGateway class.

“We’re violating dependency inversion — the business logic layer is importing directly from the infrastructure layer.”

“Once we applied dependency inversion, swapping the payment provider was just a matter of registering a different implementation.”

Interface segregation — the “I” in SOLID. It says that no client should be forced to depend on methods it does not use. Rather than one large interface, prefer several smaller, focused ones.

“That IRepository interface has 20 methods — most services only need three or four. Classic interface segregation violation.”

“We split the god interface into IReadRepository and IWriteRepository and everything became much cleaner.”

Decorator pattern — a structural pattern where a class wraps another class implementing the same interface, adding behaviour without modifying the original. Commonly used alongside DI to add cross-cutting concerns like logging or caching.

“We added a caching decorator around the repository — the container injects the decorated version transparently.”

“The logging decorator was registered in the container as a wrapper, so no service code changed at all.”

Service locator (anti-pattern) — an approach where a class calls a central registry to fetch its own dependencies, rather than having them injected. It is widely considered an anti-pattern because it hides dependencies, makes testing harder, and couples code to the locator itself.

“The old codebase was full of ServiceLocator.Get<T>() calls — we spent a sprint replacing them all with proper DI.”

“Service locator is essentially just global state with extra steps. Avoid it if you can.”

Circular dependency — a situation where class A depends on class B, which depends on class A (directly or through a chain). DI containers will throw an error or produce unpredictable behaviour when they detect this.

“The container is failing at startup with a circular dependency error between UserService and AuthService.”

“We resolved the circular dependency by extracting the shared logic into a third service that neither of the original two depends on.”


Container Configuration

Registration — the process of telling the DI container which concrete implementation to use when a particular interface or type is requested. In .NET you might write services.AddScoped<IEmailService, SmtpEmailService>().

“Don’t forget to add the registration in Startup.cs, otherwise the container will throw a resolution exception at runtime.”

“We centralised all registrations in extension methods — one AddInfrastructure() call sets everything up.”

Lifetime — the rule that governs how long a registered service instance lives. The three standard lifetimes are:

  • Singleton — one instance for the entire application lifetime.
  • Scoped — one instance per request (or per scope).
  • Transient — a new instance every time the dependency is resolved.

“We registered the database context as scoped, not singleton — a singleton DbContext causes threading issues.”

“That memory leak traced back to a transient service holding a reference to a singleton. Lifetime mismatch.”

Autowiring — a feature of many DI containers that automatically resolves constructor dependencies by inspecting type signatures, without requiring explicit per-dependency configuration. You register the types; the container figures out the wiring.

“Spring’s autowiring handles all the constructor injection automatically — we just annotate the class with @Service.”

“Autowiring is convenient, but in large projects explicit registration gives you better visibility into what’s actually being injected.”


How to Use These in Conversation

Scenario 1 — Code review comment

A colleague has written a service that creates a HttpClient inside its constructor with new HttpClient().

“This should use DI rather than instantiating HttpClient directly. Register it in the container with the correct lifetime and inject it via the constructor — that way we can mock it in tests and the container manages socket pooling properly.”

Scenario 2 — Architecture discussion

The team is debating whether to add caching to a repository layer.

“Rather than modifying the existing repository, let’s apply the decorator pattern. We wrap ProductRepository with a CachedProductRepository that implements the same IProductRepository interface, then update the container registration. The service layer won’t know or care.”

Scenario 3 — Incident post-mortem

A production bug caused by a database context captured in a background singleton worker.

“The root cause was a lifetime mismatch — the background service is registered as a singleton, but it was resolving a scoped DbContext directly. Scoped services must not be consumed by singletons. We need to use IServiceScopeFactory to create an explicit scope inside the worker.”

Scenario 4 — Onboarding a new developer

Explaining why the old service locator code is being removed.

“You’ll see some Locator.Resolve<T>() calls in the legacy modules — we’re migrating those to constructor injection. The problem with service locator is that the dependencies are hidden; you can’t tell what a class needs just by looking at its constructor. Constructor injection makes the contract explicit and the class far easier to test.”


Quick Reference

TermShort definition
Dependency injection (DI)Passing dependencies into a class rather than letting it create them
Inversion of control (IoC)Framework controls object creation and flow, not your code
DI containerFramework component that creates and wires objects automatically
Constructor injectionDependencies passed through the constructor — preferred style
Dependency inversion principleDepend on abstractions (interfaces), not concrete implementations
RegistrationMapping an interface to its concrete implementation in the container
LifetimeHow long a service instance lives: singleton / scoped / transient
Decorator patternWrapping a class to add behaviour without changing the original
Service locatorAnti-pattern: class fetches its own dependencies from a registry
Circular dependencyA → B → A dependency chain that breaks the container