What is a callback function?
A callback is a function passed into another function as an argument, to be run ("called back") later.
Synchronous callbacks — run immediately, like the function given to map or forEach.
Asynchronous callbacks — run after something finishes, like a timer, event, or network request.
Downside: deeply nested callbacks become hard to read ("callback hell") — Promises and async/await were created to solve this.
// Synchronous callback
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// Async callback
setTimeout(() => console.log('Done!'), 1000);
// Callback hell (the problem)
getUser(id, (err, user) => {
getOrders(user.id, (err, orders) => {
getDetails(orders[0].id, (err, details) => {
// Deep nesting = hard to read and maintain
});
});
});
// Solution: Promises
getUser(id)
.then(user => getOrders(user.id))
.then(orders => getDetails(orders[0].id))
.then(details => processDetails(details))
.catch(err => console.error(err));
// Solution: async/await
async function loadDetails(id) {
const user = await getUser(id);
const orders = await getOrders(user.id);
const details = await getDetails(orders[0].id);
return details;
}map takes a callback that transforms each element. setTimeout takes a callback that runs after a delay. Callback hell nests multiple async operations — unreadable.
Promise chaining flattens the nesting. async/await makes it read like synchronous code.
Show the callback hell problem, then Promise and async/await solutions. Know that callbacks are still used (event handlers, array methods).
The evolution: callbacks → Promises → async/await.