Callbacks Aren't Reasonable
Why nesting is the only way callbacks can express one step waiting on another, and why that mismatch with how your brain actually plans things causes real bugs.
Here's the actual idea this post teaches, in one sentence: nesting is the only way a plain callback can say "wait for this to finish first," but your brain naturally plans things as a straight line of steps, not as one thing nested inside another, and that mismatch is what actually makes callback code hard to follow.
The trust problem is the first of the two real issues under callback hell. Here's the second, and it has nothing to do with trust: callbacks force your brain to read code in a shape it doesn't naturally think in.
Your Brain Plans in Order
Think about how you planned this morning before you got out of bed: shower, then coffee, then get dressed, then leave. One step, then the next, then the next.
That's not a coincidence. It's how people plan almost everything. A straight line of steps, each one happening only after the one before it finishes.
Now say you need two things to happen in that order in code: place an order, then email a confirmation for it. In an imaginary world where you could pause code mid-line, you'd write exactly that:
placeOrder();
pause;
sendConfirmationEmail(); // picks up here once resumedpause isn't a real keyword. But that fantasy version is precisely how your brain wants to read this: two steps, top to bottom, in order.
What You Actually Have to Write
A plain callback can't pause and resume. The only tool it gives you for "this can't run until that finishes" is nesting one function call inside another.
function fakeAsync(value, cb) {
setTimeout(() => cb(value), 300);
}
function placeOrder(cb) {
fakeAsync({ orderId: 42 }, cb);
}
function sendConfirmationEmail(order, cb) {
fakeAsync(`Confirmation sent for order ${order.orderId}`, cb);
}
placeOrder(function (order) {
sendConfirmationEmail(order, function (message) {
console.log(message);
});
});Two steps. One dependency. And the only way to express it is burying the second step inside the first one's callback.
That gap, between the straight line your brain wants and the nested shape the code actually has, is what "not reasonable" really means. It isn't that the code is hard to read.
It's that the code forces your brain to work in a shape it doesn't naturally use.
Why the Gap Gets Worse, Not Better
Two steps is manageable. A real feature rarely stops at two.
Picture a recipe printed with each step on its own card, but the cards are shuffled into different drawers. To cook the dish, you read one card, put it down, and dig through drawers to find the next one.
You repeat that for every single step, holding the whole half-finished recipe in your head the entire time. Nothing ever shows you the full order at once.
That's what a program built entirely from nested callbacks asks your brain to do, one drawer at a time, no way to see the whole recipe.
Nobody on a real team, however senior, holds the entire flow of a deeply nested async program in their head at once. That's not a skill issue. It's the same nesting shape from callback hell, just wide enough now that no one can see all of it at once.
The fix isn't a smarter programmer. It's a pattern that lets async code read top to bottom again, the way a straight line of steps naturally does, without giving up the ability to actually wait.
That's exactly what the rest of this series builds toward, starting with a small, easy-to-miss tool called a thunk.
Keep reading