What is callback hell and how do you avoid it?
Callback hell is deeply nested callbacks where each step lives inside the previous one, making code hard to read and handle errors in. Avoid it with named functions, promises, and async/await.
Explain it simply
Booking a train ticket involves steps: log in, search trains, choose seat, pay, download ticket. If each step must start inside the previous step's "when done" function, the code moves to the right with every step and ends in a pile of closing brackets, sometimes called the pyramid of doom.
Example
The pyramid:
getUser(id, (err, user) => {
if (err) return handle(err);
getOrders(user.id, (err, orders) => {
if (err) return handle(err);
getInvoice(orders[0], (err, invoice) => {
if (err) return handle(err);
send(invoice);
});
});
});The same flow flattened with async/await:
async function sendFirstInvoice(id) {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
const invoice = await getInvoice(orders[0]);
send(invoice);
} catch (err) {
handle(err);
}
}Ways out, from lightest to best:
- Give callbacks names and move them out to top level.
- Return promises and chain
.then(). - Use
async/await, with onetry/catchcovering the whole flow. - Use
util.promisifyto convert old callback APIs.
What to say in the interview
"Callback hell is nesting caused by sequential async steps, with repeated error handling. I convert to promises and use async/await so the code reads top to bottom and has a single catch."
Common mistakes
- Thinking promises alone fix it if you still nest
.theninside.then. - Forgetting
returninside a.then, which breaks the chain.