The Trust Problem: Inversion of Control

What inversion of control actually means when you hand a callback to someone else's code, and the real bug it causes when that code doesn't behave the way you assumed.

September 5, 20263 min read6 / 17

Here's the actual idea this post teaches, in one sentence: when you hand your callback to another function, that function decides when to call it and how many times, not you, and nothing in JavaScript checks whether it does that correctly.

That's the real cost hiding under callback hell that has nothing to do with nesting. That handoff has a name: inversion of control. You had control of your own code. Now some other function has it instead.

JavaScript
someFunction(callback);

You write callback. But you never call it. someFunction does. Whatever someFunction decides, whenever it decides it, however many times it decides to, that's what happens to your code.

Most of the time this is completely fine. setTimeout always calls your callback exactly once. So does almost everything you'll ever use. You've been trusting this for years without ever thinking about it, and you've been right to.

But that trust is never actually checked. And the one time it's wrong, nothing warns you.

What It Looks Like When That Trust Breaks

Say you're building a checkout page. Right after the customer clicks "Pay," your code calls a service run by a different company, one that logs the sale. When it's done, it calls your callback, and your callback charges the card.

JavaScript
trackPurchase(orderDetails, function () { chargeCard(orderDetails); showThankYouPage(); });

It works every time you test it. You ship it.

Six months later, a customer named Neal gets charged five times for one order.

Neal clicks "Pay," once
Your code calls trackPurchase exactly one time, like always.
The other company's server has a bug
A setting meant only for their own testing went live for real customers by mistake, Neal included.
It calls your callback five times
Once a second, because it didn't get the reply it wanted fast enough.
Your callback can't tell the difference
It has no memory of already running. Every single call charges the card again.

You trusted trackPurchase to call your callback exactly once. It didn't. Nothing in your code ever checked for that, because a plain callback gives you nowhere to check it.

It's Not Just "Called Too Many Times"

The fix seems obvious: remember that it already ran.

JavaScript
let alreadyCharged = false; trackPurchase(orderDetails, function () { if (alreadyCharged) return; alreadyCharged = true; chargeCard(orderDetails); showThankYouPage(); });

That patches the one thing Neal's bug exposed. But "called too many times" was only one of several things you were trusting, without knowing you were trusting them.

⏱️Called too early
🚫Called too late, or never
❓Called with the wrong data
🔁Called more than once
🤐An error, hidden instead of shown

Every callback you've ever written was quietly hoping all five of these would go right. Most programs never check even one.

Why You Can't Just Add More Checks

You could write a check for each of these five things, for this one function. That's doable, once.

A real app has hundreds of callbacks, not one. Checking all five things, by hand, in every single one of those hundreds of places, is the same manual work as coordinating three callbacks, just repeated hundreds of times.

That's the real problem underneath callback hell: there's no shared, reliable way to make a callback keep its promises. Every program has to trust it, one callback at a time, with no way to check.

The next post covers the second real problem, one that has nothing to do with trust: why nesting callbacks breaks the way your brain naturally follows code.