Suggest an editImprove this articleRefine the answer for “What is callback hell”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Callback hell is a situation where asynchronous code is built from many callbacks nested inside one another: every next operation starts inside the callback of the previous one.** Indentation drifts to the right, an error has to be checked at every level separately, the stack trace becomes useless, and the logic cannot be reused because it is hard-wired into nested anonymous functions. The first cure is to extract callbacks into named functions, the full cure is to move to Promises or async/await. ```javascript // instead of a callback pyramid: linear code const user = await getUser('tim'); const posts = await getPosts(user.id); ``` **Key point:** the problem is not callbacks themselves but the nesting; a flat Promise chain or async/await removes it together with the duplicated error handling.Shown above the full answer for quick recall.Answer (EN)Image**Callback hell is a situation where the code uses many callback functions nested inside one another.** Because of that the code becomes hard to read, hard to maintain and practically impossible to debug: callbacks call other callbacks and form a "pyramid of doom". ## Theory ### TL;DR - Callback hell appears when every next asynchronous operation is started inside the callback of the previous one. - Visually it is a pyramid: indentation grows to the right and closing brackets pile up into a long tail. - Main problems: poor readability, duplicated error handling, tightly coupled steps, an uninformative stack trace. - The problem is typical of pre-ES6 asynchronous JavaScript: `XMLHttpRequest`, `setTimeout`, `addEventListener`, early Node.js. - It is cured by decomposition into named functions, and fully by moving to Promises and async/await. ### Quick example ```javascript // the classic "pyramid of doom" getUser('tim', (user) => { getPosts(user.id, (posts) => { getComments(posts[0].id, (comments) => { saveToDB(comments, (result) => { console.log('Done!'); }); }); }); }); ``` Here every function waits for the previous one to finish, the callbacks are nested inside each other, indentation grows to the right, errors are hard to catch, and the code turns into a forest of brackets. That is callback hell. ### Why it is bad | Problem | Description | | --- | --- | | **Poor readability** | Nesting levels make the code visually heavy | | **Hard error handling** | Every callback needs its own `if (err) ...` check | | **Chained dependencies** | Every operation waits for the previous one | | **Hard debugging** | Exceptions get lost, the stack trace is long and explains nothing | | **Code cannot be reused** | All the logic is hard-wired into nested anonymous functions | ### Where it comes from The problem is typical of asynchronous JavaScript before ES6, when everything was built on callbacks: - networking: `XMLHttpRequest`; - timers: `setTimeout`; - events: `addEventListener`; - Node.js before the Promise API. This is what typical old asynchronous code looked like: ```javascript fs.readFile('user.json', (err, data) => { if (err) throw err; getPosts(JSON.parse(data).id, (err, posts) => { if (err) throw err; sendEmail(posts, (err) => { if (err) throw err; console.log('Email sent!'); }); }); }); ``` It all works, but it looks terrible: such code is hard to maintain, test and extend. ### How to avoid callback hell #### 1. Named functions The cheapest step: move every callback into a separate named function. At least it straightens the code out. ```javascript function handleUser(user) { getPosts(user.id, handlePosts); } function handlePosts(posts) { getComments(posts[0].id, handleComments); } function handleComments(comments) { console.log('Done!'); } getUser('tim', handleUser); ``` #### 2. Promises ```javascript getUser('tim') .then(getPosts) .then(getComments) .then(saveToDB) .then(() => console.log('Done!')) .catch(console.error); ``` The code became linear, it reads top to bottom with no nesting, and a single `.catch()` covers the whole chain. #### 3. async / await ```javascript async function run() { try { const user = await getUser('tim'); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToDB(comments); console.log('Done!'); } catch (err) { console.error(err); } } run(); ``` Now the code looks synchronous but works asynchronously. No pyramids at all. A simple analogy: > Callback hell is like cooking dinner where every step requires calling a friend, that friend calls someone else, and everyone waits for everyone until a "pyramid of phone calls" grows. ### Summary | Term | Description | | --- | --- | | **Callback hell** | A situation with deeply nested callback functions | | **Cause** | Asynchronous code built on callbacks | | **Drawbacks** | Poor readability, hard debugging, duplicated logic | | **Solutions** | Promises, async/await, named functions, decomposition | ### Common mistakes - Thinking callback hell is about the amount of code. The problem is the nesting and the tight coupling of steps, not the number of lines. - Rewriting the pyramid with Promises but keeping the nesting: calling a new `.then()` inside the previous one instead of returning the Promise outwards. - Forgetting `return` inside `.then()`: the next step receives `undefined` and the chain silently falls apart. - Handling the error inside every callback separately instead of one `.catch()` or one `try/catch` around the `await` calls. - Using `await` in a loop where the operations are independent: linear code becomes slow, `Promise.all` is what is needed there. - Believing async/await removes callbacks. It only hides them: under the hood it is still Promises and the microtask queue.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.