Inversion of control means the framework calls your code, instead of your code calling the framework. In a traditional library, you write the main loop and call functions when you need them. Under inversion of control, the framework owns the main loop, the lifecycle, and the sequencing, and it calls into your code at the points it decides matter — a request handler, a lifecycle hook, an event callback. Dependency injection is the most common technique for satisfying this principle today, but it is one technique among several, not a synonym for the principle itself.
The core idea
The classic shorthand is the Hollywood principle: "don't call us, we'll call you." Compare the two control flows directly:
// Traditional control: your code drives everything
main() {
connection = openDatabase();
data = connection.query("...");
render(data);
}
// Inverted control: the framework drives, your code just plugs in
class OrderController {
onRequest(req) { // framework calls this at the right time
return this.service.handle(req);
}
}
app.register(OrderController); // you hand control to the framework
In the second example, you never write the loop that waits for a request and dispatches it — the framework does, and it calls your handler when the moment arrives. That handoff of control, not any specific syntax, is what "inversion" refers to.
The four common ways to achieve it
| Mechanism |
How control gets inverted |
Typical example |
| Dependency injection |
A container constructs your objects and hands them their dependencies |
Spring, NestJS, ASP.NET Core's built-in DI |
| Service locator |
Code asks a central registry for a dependency at the moment it needs one |
A global container.get("logger") call |
| Template method / lifecycle hooks |
A base class or framework calls your override at a fixed point in its own flow |
React class component lifecycle methods, test framework beforeEach |
| Events, callbacks, and plugins |
Your code registers a handler; the framework calls it when the event fires |
Express middleware, event emitters, webhook handlers |
Dependency injection tends to get the spotlight because DI containers are explicit and heavily marketed, but a plain event listener or a lifecycle hook is inverting control just as much — there is simply no container object involved.
Inversion of control vs dependency inversion
These two get merged constantly and they are not the same claim. Inversion of control is about who calls whom — the framework calling your code instead of the reverse. Dependency inversion (the "D" in SOLID) is a narrower design rule about which direction a dependency points: high-level code should depend on an abstraction, and low-level code should implement that same abstraction, rather than the high-level code depending on the concrete detail directly. You can apply dependency inversion inside code that never hands control to any framework at all, and you can use a framework that inverts control without following dependency inversion internally. They compose well together — a DI container is usually wiring up abstractions that were defined by following the dependency inversion principle — but neither one implies the other.
Inversion of control pays off when a framework needs to own timing and sequencing that would otherwise be duplicated in every application built on it — request routing, lifecycle management, plugin loading. The cost is indirection: understanding what actually happens when your code runs means reading the framework's contract, not just your own file, and debugging sometimes means stepping through code you did not write to see when and why it calls you.
Common mistakes
Assuming "inversion of control" means "use a DI container." A DI container is one implementation choice. Plenty of inverted-control systems — event emitters, plugin hooks, test lifecycle methods — have no container at all.
Over-relying on a service locator instead of explicit injection. A service locator hides a class's real dependencies inside a lookup call, making them invisible at the constructor and harder to test in isolation.
Fighting the framework's control flow. Reaching for global state or manual polling to work around a framework's lifecycle hooks usually means the abstraction is being misused rather than that inversion of control is the wrong choice.
Confusing IoC with dependency inversion in a design discussion. They are related but answer different questions; conflating them in a review muddies exactly what is being proposed.
FAQ
Is dependency injection required for inversion of control?
No. DI is the most visible technique, but lifecycle hooks, event callbacks, and plugin architectures all invert control without any injection container involved.
What is an IoC container, then?
A tool that constructs objects and supplies their dependencies automatically, based on registered configuration, so you do not wire the object graph by hand. It is a convenience for one specific mechanism of inversion of control, not the principle itself.
Is inversion of control the same as the Hollywood principle?
Yes — "don't call us, we'll call you" is the informal name for the same idea: the framework, not your code, decides when your code runs.
Does functional, callback-heavy code use inversion of control?
Yes. Registering a callback that a framework or event loop invokes later is inversion of control without a single class or container in sight.
Where to go next
See design patterns explained in 2026 and the factory pattern explained in 2026 for two of the building blocks IoC containers lean on internally, and which design patterns still hold up in 2026 for an opinionated look at which of those patterns earn their complexity today.