Callback in JavaScript: Why is the needed/exist?

JavaScript feels simple when code runs top to bottom. One line executes, then the next, and everything happens in order.
But real-world applications rarely work that way.
Fetching data from an API, reading files, responding to user clicks, waiting for timers — these operations take time. JavaScript needed a way to handle these tasks without freezing the entire application.
That is where callbacks came in.
Callbacks are one of the earliest and most important concepts in JavaScript, especially in asynchronous programming. Before Promises and Async/Await existed, callbacks were the primary way JavaScript handled delayed operations.
In this article, we’ll understand what callback functions are, why they exist, how they work, where they are used, and the problem developers faced with nested callbacks.
Understanding Functions as Values in JavaScript
One of JavaScript’s most powerful features is that functions are first-class citizens.
That means functions can:
Be stored in variables
Be passed as arguments
Be returned from other functions
Be treated like any other value
Example:
function greet() {
console.log("Hello Developer!");
}
const sayHello = greet;
sayHello();
Output:
Hello Developer!
Here, the function is assigned to another variable and executed through it.
This ability makes callbacks possible.
What is a Callback Function?
A callback function is simply a function passed as an argument to another function, which is executed later.
In simple words:
A callback is a function that “calls back” after another function finishes some task.
Basic example:
function greet(name) {
console.log("Hello " + name);
}
function processUser(callback) {
const user = "Mahesh";
callback(user);
}
processUser(greet);
Output:
Hello Mahesh
Here:
greetis the callback functionprocessUserreceives it as an argumentIt executes the callback later
Flow of Function Calling Another Function:
This shows the callback execution flow clearly.
Passing Functions as Arguments
Callbacks work because JavaScript allows functions to be passed like normal values.
Example:
function calculate(a, b, operation) {
return operation(a, b);
}
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
console.log(calculate(5,3,add));
console.log(calculate(5,3,multiply));
Output:
8
15
Here the behavior changes depending on which function is passed.
This makes programs flexible and reusable.
Why Callbacks Are Used in Asynchronous Programming
Now comes the real reason callbacks became essential.
JavaScript is single-threaded.
It executes one task at a time.
Imagine this:
console.log("Start");
setTimeout(function(){
console.log("Task Completed");
},2000);
console.log("End");
Output:
Start
End
Task Completed
Why?
Because JavaScript doesn’t wait for the timer.
It keeps moving and runs the callback later.
The function inside setTimeout() is a callback.
Without callbacks, JavaScript would have to pause and block execution.
Callbacks made non-blocking behavior possible.
Why This Matters
Suppose a server request takes 5 seconds.
Without asynchronous callbacks:
App freezes
User waits
UI becomes unresponsive
With callbacks:
Request runs in background
App keeps working
Callback runs when data arrives
This is why callbacks exist.
They let JavaScript handle delayed tasks efficiently.
Common Scenarios Where Callbacks Are Used
1. Event Handling
Buttons rely heavily on callbacks.
button.addEventListener("click", function(){
console.log("Button clicked");
});
When user clicks, callback executes.
2. Timers
setTimeout(function(){
console.log("Runs after 2 seconds");
},2000);
Classic callback.
3. Array Methods
Methods like forEach, map, and filter use callbacks.
const nums = [1,2,3];
nums.forEach(function(num){
console.log(num);
});
Callback runs for each element.
4. API Requests (Traditional Style)
fetchData(function(data){
console.log(data);
});
Data comes later, callback handles it.
Callback Execution Concept
Think of callbacks as giving instructions in advance.
You tell JavaScript:
“When this task finishes, run this function.”
That “run this later” behavior is the callback idea.
The Problem: Callback Nesting
Callbacks solved asynchronous problems.
But they introduced another problem.
Nested callbacks.
Example:
loginUser(function(user){
getProfile(user,function(profile){
getPosts(profile,function(posts){
getComments(posts,function(comments){
console.log(comments);
});
});
});
});
Looks messy already.
This is called:
Callback Hell
or
Pyramid of Doom
Problems:
Hard to read
Difficult to debug
Error handling becomes messy
Deep nesting reduces maintainability
Nested Callback Execution Flow
For callback hell pyramid style:
This visually shows nesting.
Why Callback Nesting Became a Problem
Each async task depended on previous data.
So developers kept nesting callbacks inside callbacks.
Like:
“If login succeeds, get profile”
“If profile loads, get posts”
“If posts load, get comments”
This chaining created deeply indented code.
That problem eventually led to:
Promises
Async/Await
But callbacks were the foundation.
Key Takeaways
Callbacks exist because JavaScript needed a way to handle delayed tasks without blocking execution.
A callback is:
A function passed into another function
Executed later when needed
Essential for asynchronous programming
Used in:
Events
Timers
Array methods
API calls
File operations
Challenge:
- Too much nesting causes callback hell.
Still, understanding callbacks is crucial because modern tools like Promises and Async/Await are built on the same idea.