JavaScript Callbacks
Understand synchronous vs asynchronous callbacks, the error-first pattern, and why Callback Hell led to Promises.
1. What is a Callback Function?
A callback is simply a function that you pass into another function as an argument, to be called when some work is finished.
💡 Think of a Fast Food Buzzer
When you order a burger, you don't stand at the register freezing the whole line. The cashier hands you a buzzer (the callback). You go sit down, and when your food is ready, the buzzer buzzes (executes your callback) to notify you!
function prepareFood(foodName, onReady) {
console.log(`Cooking ${foodName}...`);
// When done, run the callback function:
onReady(`${foodName} is hot and ready! 🍔`);
}
function notifyCustomer(message) {
console.log("Customer Alert:", message);
}
// Pass 'notifyCustomer' as a callback (do NOT add parentheses () when passing!)
prepareFood("Burger", notifyCustomer);
Cooking Burger... Customer Alert: Burger is hot and ready! 🍔
2. Synchronous vs Asynchronous Callbacks
Callbacks can run immediately (sync) or wait for a timer/API (async):
1. Synchronous (Immediate ⚡)
Runs right away before moving to the next line of code (e.g. .map(), .filter()).
[1, 2].forEach(n => console.log(n));
console.log("Done"); // Prints after loop
2. Asynchronous (Delayed ⏳)
Pauses in the background (timers, network requests) and runs after the main script completes.
setTimeout(() => console.log("Timer"), 0);
console.log("Done"); // Prints BEFORE timer!
3. The Error-First Callback Pattern
In Node.js and classic JavaScript APIs, the first input of a callback is always reserved for an error. If there's no error, it passes null:
function fetchScore(studentId, callback) {
setTimeout(() => {
if (studentId <= 0) {
// 1. First argument is the Error, second is null:
return callback(new Error("Invalid Student ID!"), null);
}
// 2. On success: error is null, second argument is the data:
callback(null, { id: studentId, score: 95 });
}, 50);
}
fetchScore(101, (err, data) => {
if (err) {
return console.log("Error:", err.message);
}
console.log("Success! Score is:", data.score);
});
Success! Score is: 95
4. Callback Hell: Why Promises Were Created
When you have multiple async actions in a row (e.g. login -> fetch profile -> load messages -> play notification), nesting callbacks inside callbacks creates a giant messy triangle called Callback Hell:
⚠️ The "Pyramid of Doom"
loginUser(credentials, (err, user) => {
getProfile(user.id, (err, profile) => {
getMessages(profile.inboxId, (err, messages) => {
renderScreen(messages, (err) => {
// Hard to read, hard to debug, error handling is duplicated!
});
});
});
});
Modern JavaScript solved this problem with Promises and async/await, which let you write flat, readable asynchronous code.
5. Quick Callback Summary
| Concept | What it Means | Rule to Remember |
|---|---|---|
| Callback | A function passed into another function | Pass the reference fn, not the invocation fn() |
| Sync Callback | Executes immediately in sequence | Blocks following lines until finished (e.g. .map()) |
| Async Callback | Executes after background task finishes | Never blocks main thread (e.g. setTimeout) |
| Error-First | callback(err, data) format |
Always check if (err) before using data |
6. Callbacks Interview Deck
Test your understanding on async timing, passing references vs invocations, error-first conventions, and callback hell:
7. Practice Challenge
🎯 Coding Challenge: Async Error-First Calculator
1. Create an asynchronous function calculateAsync(a, b, callback) that uses setTimeout.
2. If either input is not a number, pass new Error(...) as the first argument.
3. If valid, pass null as the first argument and the sum a + b as the second argument.