Callbacks in JavaScript: Why They Exist

Search for a command to run...

No comments yet. Be the first to comment.
Node.js doesn't use multiple threads to handle concurrency — and that's not a weakness. Here's exactly how it pulls off handling thousands of connections without breaking a sweat.

Stop confusing params and query in Express.js

Table of Contents What is cURL — in very simple terms Why programmers need cURL Making your first request Understanding request and response Using cURL to talk to APIs Common mistakes beginners

A practical guide to where files live, how to expose them over HTTP, and the security rules you cannot skip.

Understanding authentication approaches · stateful vs stateless · when to use each

XP-VISHAL-WEB-DEV-COHORT-2026-BLOGS
24 posts
In JavaScript, functions are first-class citizens. This means they can be treated like any other value:
Assigned to variables
Stored in arrays or objects
Passed as arguments to other functions
Returned from functions
const greet = function(name) {
console.log("Hello, " + name);
}; // function can be assigned to variables
const functions = [greet, (x) => x * x];
// They can be stored in data structures
Because function are the values. Can be passed to other function — this is the essence of the callback.
Callback is a function that is passed as argument to another function and is executed later, often after some operation has completed.
const processUserInput (name, callback){
// do some processing
const processedName = name.trim().toUpperCase();
// then use the callback with the result
callback(processedName );
}
function greetUser(name) {
console.log(`Hello, ${name}!`);
}
processUserInput(" Alia ", greetUser);
// Output: Hello, ALIA!
Here, greetUser is the callback. The processUserInput function calls back the provided function after its own work is done.
JavaScript is single‑threaded. If a task takes a long time (e.g., reading a file, waiting for a network response), it would block the entire program. To avoid this, JavaScript uses asynchronous operations that run in the background and notify the program when they finish—often via callbacks.
setTimeoutconsole.log("Start");
setTimeout(() => {
console.log("Callback executed after 2 seconds");
}, 2000);
console.log("End");
// Output:
// Start
// End
// (after 2 seconds) Callback executed after 2 seconds
The callback passed to setTimeout runs later, without blocking the rest of the code. This non‑blocking behaviour is why callbacks are indispensable in JavaScript.
document.getElementById("btnid12".addEventListener("click", () => {
console.log("Button was clicked!");
})
The callback runs only when the user clicks the button—again, an asynchronous event.
Functions that accept other functions as arguments (or return them) are called higher‑order functions. Callbacks are a classic example.
Array methods like map, filter, and forEach rely on callbacks:
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // [2, 4, 6, 8]
Here the anonymous function is the callback that map calls for each element.
Passing functions as arguments allows separation of concerns. The higher‑order function handles the repetitive logic (like iterating over an array), while the callback defines the custom operation.
Timers: setTimeout, setInterval
DOM Events: addEventListener, onclick, etc.
Network Requests: fetch (with .then is Promise‑based, but under the hood callbacks are used; also older XMLHttpRequest)
File System: Node.js fs.readFile uses callbacks
Database Queries: In Node.js, many database libraries use callbacks
Custom Higher‑Order Functions: You can create your own functions that accept callbacks
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content:', data);
});
console.log('Reading file...'); // Runs before the file is read
When you have multiple asynchronous operations that depend on each other, you end up nesting callbacks inside callbacks. This quickly becomes hard to read and maintain—a phenomenon known as callback hell or the pyramid of doom.
getUser(1, (user) => {
console.log('User:', user);
getOrders(user.id, (orders) => {
console.log('Orders:', orders);
getOrderDetails(orders[0].id, (details) => {
console.log('Details:', details);
// More nested operations...
});
});
});
Readability: The code indents deeper with every asynchronous step.
Error Handling: Each callback must handle errors individually; there is no central place.
Maintainability: Adding, removing, or modifying steps is error‑prone.
Reusability: Hard to extract and reuse pieces of logic.
Each operation waits for the previous one, and the nesting creates a deep chain.
Conclusion
Callbacks are a direct consequence of JavaScript’s function‑as‑value nature and its need for non‑blocking asynchronous operations. They enable powerful patterns like event handling, iteration, and customisable behaviour in higher‑order functions. However, when used for sequential asynchronous tasks, they can lead to deeply nested code that is hard to manage.
Modern JavaScript offers Promises and async/await to address these nesting issues while still relying on the same callback concept under the hood. Nevertheless, callbacks remain essential to understand, as they form the foundation of JavaScript’s concurrency model and appear in countless APIs.