What was 'callback hell' and how did promises and async/await solve it?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
Callback hell was deeply nested callbacks for sequential async work, producing unreadable, error-prone pyramids. Promises flattened sequences into chains, and async/await made them read like synchronous code with try/catch error handling.
Detailed explanation
Early Node-style APIs took callbacks with the convention cb(error, result). Chaining several operations meant nesting each step inside the previous callback, growing indentation rightward — hard to read, impossible to refactor, with error handling duplicated at every level. Inversion of control added risk: you handed your continuation to someone else's code, which might call back twice or never. Promises fixed control by making the continuation an object you own: chains are flat, errors funnel into one .catch, and guarantees ensure exactly one settlement. Async/await completed the shift syntactically: same machinery, but written top-to-bottom like blocking code, integrating with try/catch and standard debugging tools like breakpoints and stack traces that follow logical flow.
Example
// Callback pyramid:
getUser(id, (err, user) => {
if (err) return fail(err);
getPosts(user, (err, posts) => {
if (err) return fail(err);
getComments(posts[0], (err, comments) => {
if (err) return fail(err);
done(comments);
});
});
});
// Same logic today:
try {
const user = await getUser(id);
const posts = await getPosts(user);
done(await getComments(posts[0]));
} catch (err) { fail(err); }Interview tip
Show the pyramid collapsing into linear code side-by-side — visual contrast communicates the evolution better than history alone.
Common mistakes
- Only saying 'callbacks are bad' without naming inversion-of-control issues.
- Not recognizing the (err, result) Node convention.