Callbacks in JavaScript: Why They Exist
JavaScript treats functions like values. You can store them, pass them to another function, and call them later. That flexibility is the reason callbacks exist.
This article moves from simple function arguments to asynchronous programming and callback nesting.
Functions are values
function sayHello() {
console.log("Hello!");
}
const greet = sayHello;
greet(); // Hello!
Enter fullscreen mode Exit fullscreen mode
sayHello is the function itself. sayHello() runs it.
What is a callback?
A callback is a function passed into another function so that it can be run later.
function welcome(name, afterWelcome) {
console.log(`Welcome, ${name}!`);
afterWelcome();
}
function showMenu() {
console.log("Here is today's menu.");
}
welcome("Vasu", showMenu);
Enter fullscreen mode Exit fullscreen mode
flowchart LR
A[Call welcome] --> B[Print greeting]
B --> C[Run callback]
C --> D[Show menu]
Enter fullscreen mode Exit fullscreen mode
We pass showMenu, not showMenu(), because welcome decides when to call it.
Why callbacks are useful
Callbacks make code reusable. One function can provide data while another function decides what to do with it.
function processNumber(number, action) {
return action(number);
}
console.log(processNumber(5, (value) => value * 2)); // 10
console.log(processNumber(5, (value) => value * value)); // 25
Enter fullscreen mode Exit fullscreen mode
Common callback scenarios
Arrays
const prices = [100, 250, 80];
const discounted = prices.map((price) => price * 0.9);
const expensive = prices.filter((price) => price > 100);
Enter fullscreen mode Exit fullscreen mode
map and filter call your callback for each array item.
Click events
button.addEventListener("click", () => {
console.log("Button clicked");
});
Enter fullscreen mode Exit fullscreen mode
The browser runs this callback only when a user clicks.
Timers
console.log("Order received");
setTimeout(() => {
console.log("Food is ready");
}, 2000);
console.log("Preparing payment");
Enter fullscreen mode Exit fullscreen mode
The timeout callback runs later, so JavaScript can continue with other work first.
Callbacks and asynchronous JavaScript
Slow tasks—network calls, timers, files, database work—should not freeze the app. JavaScript starts the task, keeps running other code, and executes a callback when the task finishes.
flowchart LR
A[Start async task] --> B[Continue other work]
B --> C[Task completes]
C --> D[Callback runs]
Enter fullscreen mode Exit fullscreen mode
Here is a classic Node.js-style callback:
readFile("notes.txt", "utf8", (error, content) => {
if (error) {
console.error(error);
return;
}
console.log(content);
});
Enter fullscreen mode Exit fullscreen mode
Many Node.js APIs use the “error-first” callback pattern: check the error first, then use the result.
The drawback: callback nesting
When several asynchronous steps depend on each other, callbacks can become deeply nested:
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getPaymentStatus(orders[0].id, (status) => {
sendNotification(status, () => {
console.log("All done");
});
});
});
});
Enter fullscreen mode Exit fullscreen mode
This is known as callback hell. The issue is readability, repeated error handling, and difficulty changing the flow—not that callbacks are inherently bad.
Modern JavaScript often uses Promises and async/await for these sequences:
async function notifyAboutFirstOrder(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const status = await getPaymentStatus(orders[0].id);
await sendNotification(status);
}
Enter fullscreen mode Exit fullscreen mode
Final takeaway
Callbacks let JavaScript say: “When this work is done, run this next function.” They power array methods, browser events, timers, and asynchronous APIs.
Learn callbacks first—the ideas behind Promises and async/await become much easier afterward.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.