JavaScript is a language where functions are treated like values. This means you can store them in variables, pass them to other functions, and return them from functions. This ability is what makes callbacks possible.
Callbacks are one of the most important concepts in JavaScript, especially when dealing with asynchronous programming like API calls, timers, and event handling.
Functions as Values in JavaScript
In JavaScript, functions are first-class citizens, which means they can be used just like any other variable.
function greet() {
console.log("Hello!");
}
const sayHello = greet;
sayHello();
Here, the function greet is assigned to sayHello, and then called using the new variable.
functions as arguments to other functions.
function greetUser(name, callback) {
console.log("Hi " + name);
callback();
}
function sayBye() {
console.log("Goodbye!");
}
greetUser("Sakshi", sayBye);
Output:
Hi Sakshi
Goodbye!
In this example, sayBye is passed into greetUser as an argument. This passed function is called a callback function.
What is a Callback Function is
A callback function is a function that is passed as an argument to another function and is executed later.
It “calls back” after the main function finishes its work.
Example:
function processData(callback) {
console.log("Processing data...");
callback();
}
processData(function() {
console.log("Data processed successfully!");
});
Output:
Processing data...
Data processed successfully!
- processData() starts running
- It prints "Processing data..."
- Then it executes the callback function
Why Callbacks Exist
Callbacks exist because sometimes JavaScript needs to wait for a task to complete before running the next step.
Examples:
- Fetching data from a server
- Waiting for a user click
- Reading a file
- Running a timer
Why callbacks are used in asynchronous programming
JavaScript runs in a single thread, meaning it executes one task at a time.
console.log("Start");
setTimeout(function() {
console.log("Done after 2 seconds");
}, 2000);
console.log("End");
Output:
Start
End
Done after 2 seconds
Even though setTimeout() appears in the middle, JavaScript does not wait 2 seconds.
Instead:
- "Start" is printed
- Timer begins in the background
- "End" is printed immediately
- After 2 seconds, callback runs
This is asynchronous behavior.
Without callbacks, JavaScript would have to pause execution until the task complete.
Callbacks allow JavaScript to remain fast and non-blocking.
Passing functions as arguments
Callbacks rely on the ability to pass functions as arguments.
function calculator(a, b, operation) {
operation(a, b);
}
function add(x, y) {
console.log(x + y);
}
calculator(5, 3, add);
Output:
8
- calculator() receives numbers and a function
- add() is passed as the callback
- calculator() runs the callback with the numbers
This pattern is widely used in JavaScript.
Callback usage in common scenarios
Callbacks are commonly used in real-world JavaScript.
- Event Handling
When a button is clicked, a callback runs.
button.addEventListener("click", function() {
console.log("Button clicked!");
});
The click event may happen later, so JavaScript stores the callback until needed.
- Timers
setTimeout(function() {
console.log("Executed later");
}, 1000);
The callback executes after 1 second.
- Array Methods
Functions like forEach() use callbacks.
const numbers = [1, 2, 3];
numbers.forEach(function(num) {
console.log(num);
});
Output:
1
2
3
The callback runs once for each array element.
The Basic Problem of Callback Nesting
Callbacks are useful, but when multiple asynchronous tasks depend on each other, callbacks can become deeply nested.
loginUser(function(user) {
getProfile(user, function(profile) {
getPosts(profile, function(posts) {
console.log(posts);
});
});
});
This structure keeps growing inward.
This is called callback nesting or callback hell.
Problems include:
- Hard to read
- Difficult to debug
- Difficult to maintain
- Error handling becomes messy
This pyramid shape is often called the “Pyramid of Doom.”
Explain callback problems conceptually
Imagine:
- Login user
- Get profile
- Get posts
- Display posts Each step waits for the previous one. That creates nested callbacks:
step1(function() {
step2(function() {
step3(function() {
step4(function() {
console.log("Done");
});
});
});
});
The more steps you add, the harder the code becomes to manage.
This is why JavaScript introduced Promises and async/await to improve asynchronous code readability.
- Function Calling Another Function Flow
Main Function
|
v
Executes Callback
|
v
Callback Function Runs
- Nested Callback Execution Flow
Task 1
|
--> Task 2
|
--> Task 3
|
--> Task 4
Or visually:
step1(
step2(
step3(
step4()
)
)
)













