Suggest an editImprove this articleRefine the answer for “Callback hell”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Callback hell appears when several asynchronous operations are nested inside one another and each one depends on the result of the previous: the code turns into a forest of nested functions that drifts to the right like a staircase.** Such code is hard to read and debug, error handling has to be repeated on every level, and individual steps are almost impossible to reuse. The fix is Promises and `async/await`, which express the same sequence flatly with a single shared `try/catch`. ```javascript // before: nesting grows with every step getUser(id, user => { getPosts(user.id, posts => { getComments(posts[0].id, comments => { saveToFile(comments, () => console.log('Done!')); }); }); }); // after: linear code const user = await getUser(id); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToFile(comments); ``` **Key point:** the problem is not callbacks themselves but deep nesting and an unmanageable control flow; Promises and `async/await` make that flow linear.Shown above the full answer for quick recall.Answer (EN)Image**Callback hell is the state your code reaches when several asynchronous operations are nested inside one another because each one depends on the result of the previous, and the program turns into a forest of nested functions.** Such code is hard to read and debug, error handling is awkward, and changing or reusing any part of it is painful. ## Theory ### TL;DR - The cause: sequential asynchronous steps where each one needs the result of the previous one. - The symptom: the code drifts to the right like a staircase, one nesting level per step. - The consequences: poor readability, duplicated error handling, no way to reuse a single step. - The problem is not callbacks themselves but deep nesting and an unmanageable control flow. - The cure: Promises and `async/await`, which express the same sequence linearly. ### Quick example ```javascript getUser(id, user => { getPosts(user.id, posts => { getComments(posts[0].id, comments => { saveToFile(comments, () => { console.log('Done!'); }); }); }); }); ``` Four steps, and already four levels of nesting. Add a fifth and it becomes even harder to read. ### Why the nesting appears A callback does not return a value to the caller: the result arrives later, inside the function you passed in. So the only way to use that result in the next step is to write the next call **inside** the callback. Every dependency between steps adds one nesting level, and a sequence of N operations gives you N levels of indentation. That is how logic which is linear in meaning ("get the user, then their posts, then the comments, then save") becomes pyramidal in shape. This shape is known as the pyramid of doom. ### Why it hurts - **Readability.** To follow the order of steps you have to read the nesting structure rather than a sequence of lines. The end of each step disappears among the closing brackets. - **Error handling.** There is no shared place for failures: in the error-first style every level has its own `err` parameter, and the same check has to be repeated over and over. - **Change and reuse.** Extracting a step or reordering steps is hard: they are physically nested inside one another instead of sitting side by side. ```javascript getUser(id, (err, user) => { if (err) return handle(err); getPosts(user.id, (err, posts) => { if (err) return handle(err); getComments(posts[0].id, (err, comments) => { if (err) return handle(err); // the useful work hides under three checks }); }); }); ``` ### Untangling it: Promises and async/await A Promise returns a value to the caller, so the next step can be placed **next to** the previous one instead of inside it. A `.then` chain already flattens the code, and `async/await` makes it look almost synchronous: ```javascript const user = await getUser(id); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToFile(comments); ``` The same code with error handling in a single place: ```javascript async function run(id) { try { const user = await getUser(id); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); await saveToFile(comments); console.log('Done!'); } catch (err) { console.error(err.message); } } ``` If the steps are independent, they do not need to be lined up sequentially: `Promise.all` starts them in parallel and waits for all of them at once. ```javascript const [user, settings] = await Promise.all([getUser(id), getSettings(id)]); ``` When an old API only speaks callbacks, you wrap it into a Promise once (`util.promisify` in Node.js, or your own wrapper) and write everything downstream linearly. ### Common mistakes - Believing callbacks are bad in themselves. It is deep nesting that is bad; `map`, `forEach` and event handlers are callbacks too and they are perfectly fine. - Handling the error only at the deepest level: a failure in the first step is simply lost. - "Fixing" the nesting by extracting every callback into a named function: there are fewer visual levels, but the control flow is still scattered across the file. - Putting `await` inside a loop for independent operations: the sequence becomes slower where `Promise.all` belonged. - Forgetting that inside a callback `return` returns from the callback, not from the enclosing function.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.