In JavaScript function are treated as values just like normal (numbers , string and arrays etc).
That means you can :
1. You can store a function in a variable.
2. You can pass it as an argument to another function.
3. You can return a function from another function.
What is Callback? :
A callback is a function that is passed to another function as an argument and is executed later. It is used to delay the execution of a function.
function greet(name, callback) {
console.log("Hello " + name);
callback(); // calling the callback
}
function sayBye() {
console.log("Bye");
}
greet("John", sayBye);
Output :
Hello John
Bye
Why callbacks are used in asynchronous programming :
Callbacks are used to delay the execution of a function.
Scenario: Food Order System :
You order food and it takes time to prepare.
You give phone number -> call me when ready (callback).
Code Example :
function orderFood(callback) {
console.log("Order placed...");
setTimeout(function() {
console.log("Food is ready ");
callback(); // calling you back
}, 3000);
}
function notifyUser() {
console.log("Hey Abhi, your order is ready! Come pick it up");
}
// placing order
orderFood(notifyUser);
Output :
Order placed...
(3 sec later...)
Food is ready
Hey Abhi, your order is ready! Come pick it up
Let's break down above code to understand use case of callback.
First, the orderFood function runs.
“Order placed...” is printed immediately.
After that, setTimeout starts a timer for 3 seconds.
JavaScript does not wait here and continues its work.
After 3 seconds, the message “Food is ready ” is printed.
Then, the function notifyUser is called.
Finally, the user can see the notification.
Basic problem of callback nesting:
When multiple asynchronous tasks depend on each other, developers often use a chain of callbacks to execute them. However, this approach makes the code difficult to read, maintain, and debug. This issue is known as callback hell or the pyramid of doom.
function step1(callback) {
setTimeout(() => {
console.log("Step 1 done");
callback();
}, 1000);
}
function step2(callback) {
setTimeout(() => {
console.log("Step 2 done");
callback();
}, 1000);
}
function step3(callback) {
setTimeout(() => {
console.log("Step 3 done");
callback();
}, 1000);
}
// nesting starts here
step1(() => {
step2(() => {
step3(() => {
console.log("All steps completed");
});
});
});
step1(() => {
step2(() => {
step3(() => {
Look at this part of code is growing horizontally in a pyramid shape.
Solution of Callback hell :
To avoid this you should use promises and async/await.
Promises : Promise solve the callback nesting by making the code readable.
Same code example using promises:
function step1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("Step 1 done");
resolve();
}, 1000);
});
}
function step2() {
return new Promise(resolve => {
setTimeout(() => {
console.log("Step 2 done");
resolve();
}, 1000);
});
}
function step3() {
return new Promise(resolve => {
setTimeout(() => {
console.log("Step 3 done");
resolve();
}, 1000);
});
}
// clean chaining
step1()
.then(step2)
.then(step3)
.then(() => {
console.log("All steps completed");
});
Async/Await :
Async/Await makes asynchronous code look like synchronous code.
Same example using Async/Await :
async function runSteps() {
await step1();
await step2();
await step3();
console.log("All steps completed ");
}
runSteps();
Summary:
In this blog, we learned how callbacks work in JavaScript using a simple real-world example. Callbacks allow us to run a function after a task is completed, especially in asynchronous operations.
However, when multiple asynchronous tasks depend on each other, using callbacks can lead to deeply nested code, making it difficult to read, maintain, and debug. This problem is known as callback hell or the pyramid of doom.
To solve this issue, we use Promises, which help us write cleaner and more structured code using .then() chaining. Further, async/await makes asynchronous code look like synchronous code, improving readability and making it easier to handle errors.
Top comments (1)
Nice breakdown of callbacks! The food order analogy is a great way to explain async behavior to beginners. One tip worth adding: once you get comfortable with callbacks, look into Promises and async/await as they solve the nested callback problem (callback hell) and make async code much easier to read and maintain.