Callbacks in JavaScript: Why They Exist
Learn why callbacks exist and how they work

Hi, I’m Abdul Samad. A web development learner and tech enthusiast. I write about what I learn, share practical coding tips, and publish in-depth blogs on programming and modern web development.
Check out my full collection of blogs on Hashnode: https://abdulsamad30.hashnode.dev/
Connect with me on X for quick updates and insights: @abdul_sama60108
JavaScript is a language that treats functions like first-class citizens.
That sounds fancy, but it just means one thing ' functions are values '
You can store them in variables, pass them around, return them from other functions.
This single idea is what makes callbacks possible, and understanding it is very important
Functions Are Values
Before we even get to callbacks, let's sit with this idea for a second.
function greet(name) {
console.log("Hello, " + name);
}
const sayHello = greet;
sayHello("Samad"); // Hello, Samad
greet didn't get called here. It got assigned. You're passing the function itself, not its result. This is the foundation everything else builds on.
What Is a Callback Function?
A callback is just a function you pass into another function, so that other function can call it at the right time.
That's it. No magic.
function doSomething(callback) {
console.log("Doing something...");
callback();
}
function done() {
console.log("All done!");
}
doSomething(done);
Output:
Doing something...
All done!
You're not calling done() directly. You're handing it to doSomething and saying: "Call this when you're ready."
Why Callbacks Exist in Async Programming
JavaScript runs on a single thread. It can only do one thing at a time.
But some operations, like fetching data from a server, reading a file, or waiting for a timer, take time.
If JavaScript just sat there waiting, your entire page would freeze.
So instead of waiting, JavaScript says: "Start this task, and when it's done, call this function." That function it calls when the task completes is the callback.
console.log("Before");
setTimeout(function () {
console.log("Inside timeout");
}, 2000);
console.log("After");
Output:
Before
After
Inside timeout
Notice that "After" prints before "Inside timeout" even though it's written after the setTimeout.
JavaScript didn't wait. It moved on and came back to the callback when the timer expired.
This is why callbacks exist. They're how you say "do this, but when it's done, run this."
Passing Functions as Arguments
You've already seen this, but let's make it concrete with a cleaner example.
function processData(data, callback) {
const result = data.toUpperCase();
callback(result);
}
processData("hello world", function (output) {
console.log("Processed:", output);
});
Output:
Processed: HELLO WORLD
The function doing the work, processData, doesn't care what happens with the result.
That's the caller's problem. You give it a callback, it passes the result in, and you decide what to do with it. This separation keeps functions clean and reusable.
Callbacks in Common Scenarios
Callbacks show up everywhere in JavaScript. Here are three you'll run into constantly.
Event Listeners
document.getElementById("btn").addEventListener("click", function () {
console.log("Button clicked!");
});
The second argument is a callback. The browser holds onto it and calls it only when the click happens.
Array Methods
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(function (num) {
return num * 2;
});
console.log(doubled);
Output:
[2, 4, 6, 8]
map runs your callback once per element and builds a new array from the return values.
Timers
setTimeout(function () {
console.log("Ran after 1 second");
}, 1000);
Classic. The callback runs after the delay, not immediately.
The Problem: Callback Nesting
Callbacks work great until you need to chain multiple async operations. What if after one thing finishes, you need to do another, and after that another?
setTimeout(function () {
console.log("Step 1 done");
setTimeout(function () {
console.log("Step 2 done");
setTimeout(function () {
console.log("Step 3 done");
}, 1000);
}, 1000);
}, 1000);
Output:
Step 1 done
Step 2 done
Step 3 done
It works, but look at the shape of that code.
Every new step pushes everything further right. This is called callback hell, or the pyramid of doom. The deeper you go, the harder it is to read, debug, or maintain.
This isn't a performance problem.
It's a readability and maintainability problem. And it's exactly what Promises and async/await were designed to solve, but that's a conversation for another blog.
Wrapping Up
Callbacks exist because JavaScript is single-threaded and async by nature. They're how you hand off control and say "run this when you're done." They're used in event handling, array methods, timers, and almost every async operation you'll write.
The concept is simple. The problems come when you start nesting them too deep. That's when you start reaching for better tools, but you can't fully appreciate those tools without first understanding why callbacks exist and where they fall short.
FIN ✌️




