What is callback hell?

JavaScript

Callback hell

In JavaScript, the only way to delay a computation so that it runs after the asynchronous call completes is to put the delayed code inside a callback function. When multiple asynchronous operations need to be chained together, this can lead to deeply nested callbacks, known as "callback hell" or the "pyramid of doom."

getData(callbackFunction(x) {
getMoreData(anotherCallbackFunction(y) {
updateUI();
})
})

Now, in the example given above we make an asynchronous request on click of a button. If the request succeeds, we make a second request. If the second request succeeds, we update our UI by using the data which we are getting from both requests. This pattern leads us to nested callbacks, which is called callback hell.

Real-World Example

Let's look at two ways to implement the same functionality - one showing callback hell and another showing how to improve it.

The Callback Hell Version

listen("click", function handler(evt) {
setTimeout(function request() {
ajax("url", function response(text) {
if (text == "hello") {
handler();
} else if (text == "world") {
request();
}
});
}, 500);
});

This version demonstrates callback hell because:

  • It has multiple levels of nesting (3 levels deep)
  • The code reads from left to right, making it harder to follow
  • Anonymous functions make debugging more difficult
  • The pyramid-like structure reduces readability

The Improved Version

listen("click", handler);

function handler() {
setTimeout(request, 500);
}

function request() {
ajax("url", response);
}

function response(text) {
if (text == "hello") {
handler();
} else if (text == "world") {
request();
}
}

This refactored version solves the callback hell problem by:

  • Breaking down nested callbacks into named functions
  • Improving readability through proper function separation
  • Making the code more maintainable and testable
  • Reducing cognitive load when reading the code
  • Maintaining the same functionality while being more organized

While modern JavaScript often uses Promises or async/await to handle asynchronous operations, this example demonstrates how even callback-based code can be written in a more maintainable way through proper organization and naming.

00:00