Callback Functions in JavaScript
A callback is a function passed as an argument to another function and called ("called back") later — either synchronously (immediately) or asynchronously (after an operation completes). Callbacks are a fundamental concept in JavaScript, forming the basis of event handling, array methods, Promises, and async/await.
What Is a Callback
// Functions as first-class citizens — can be passed around like variables
function greet(name) {
console.log(`Hello, ${name}!`);
}
function execute(fn, argument) {
fn(argument); // called here
}
execute(greet, "John"); // "Hello, John!"
// Callback — a function called after an operation completes
function completeTask(taskName, callback) {
console.log(`Processing: ${taskName}...`);
// Simulated work
const done = true;
if (done) {
callback(null, taskName); // success
} else {
callback(new Error("Assignment failed"), null);
}
}
// Usage
completeTask("Algorithms course", function(error, result) {
if (error) {
console.error(error.message);
} else {
console.log(`✅ Assignment done: ${result}`);
}
});
// Processing: Algorithms course...
// ✅ Assignment done: Algorithms course
Code Example: Synchronous vs Asynchronous Callback
// === SYNCHRONOUS CALLBACK ===
// Called immediately, before the next line is executed
const courseList = ["Algorithms", "Database", "Web Dev", "Networking"];
// forEach, map, filter — synchronous callbacks
courseList.forEach(function(course, index) {
console.log(`${index + 1}. ${course}`);
});
// Output appears immediately, line by line
// === ASYNCHRONOUS CALLBACK ===
// Called later, after the async operation completes
console.log("1. Start");
setTimeout(function() {
console.log("2. This is async (appears later)");
}, 1000);
console.log("3. Done");
// Output:
// 1. Start
// 3. Done
// 2. This is async (appears later) ← after 1 second
// Callbacks with array methods
const student = [
{ name: "John", gpa: 3.75, credits: 13 },
{ name: "Amy", gpa: 3.90, credits: 14 },
{ name: "Mike", gpa: 2.80, credits: 12 },
{ name: "Anna", gpa: 3.50, credits: 15 },
];
// filter — synchronous callback
const cumLaude = student.filter(function(student) {
return student.gpa >= 3.5;
});
console.log(cumLaude.map(m => m.name)); // ["John", "Amy", "Anna"]
// map — synchronous callback
const studentName = student.map(function(student) {
return student.name;
});
console.log(studentName); // ["John", "Amy", "Mike", "Anna"]
// find — synchronous callback
const found = student.find(function(student) {
return student.name === "Mike";
});
console.log(found); // { name: "Mike", gpa: 2.80, credits: 12 }
// reduce — synchronous callback
const totalCredits = student.reduce(function(acc, student) {
return acc + student.credits;
}, 0);
console.log(`Total credits: ${totalCredits}`); // 54
Callback Hell
When callbacks are nested too deeply, the code becomes hard to read and maintain — this is called callback hell.
// ❌ CALLBACK HELL — multi-level nested callbacks
function login(username, callback) {
setTimeout(() => {
console.log("1. Login successful");
callback({ token: "abc123" });
}, 500);
}
function getProfile(token, callback) {
setTimeout(() => {
console.log("2. Profile fetched");
callback({ name: "John", studentId: "2023001" });
}, 500);
}
function getGrade(studentId, callback) {
setTimeout(() => {
console.log("3. Grade fetched");
callback([{ course: "Algorithms", grade: 85 }]);
}, 500);
}
function display(grade, callback) {
setTimeout(() => {
console.log("4. Grade displayed");
callback(grade);
}, 500);
}
// ❌ Callback hell — hard to read!
login("john", function(result1) {
getProfile(result1.token, function(profile) {
getGrade(profile.studentId, function(grade) {
display(grade, function(final) {
console.log("Done:", final);
});
});
});
});
// ✅ SOLUTION: Promises or async/await (next chapter)
Code Explanation
| Type | Explanation | Example |
|---|---|---|
| Sync callback | Executed immediately | array.map(), array.forEach() |
| Async callback | Executed later | setTimeout(), fetch(), fs.readFile() |
| Callback hell | Callbacks nested too deeply | Nested callbacks 3+ levels deep |
| Error-first callback | (error, result) => {} |
Node.js convention |
Error-first callback convention:
- First parameter: the error (or null if successful)
- Second parameter: the operation's result
Real-World Examples
1. Callback After Form Submit
// Simulated form submit with sequential validation
function submitForm(formData, callback) {
console.log("Validating data...");
// Validate name
if (!formData.name || formData.name.length < 3) {
return callback(new Error("Name at least 3 characters"), null);
}
// Validate email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formData.email)) {
return callback(new Error("Invalid email"), null);
}
// Validate StudentID
if (!/^\d{8}$/.test(formData.studentId)) {
return callback(new Error("StudentID must be 8 digits"), null);
}
// Simulate sending to the server
setTimeout(() => {
callback(null, {
status: "success",
message: "Registration successful!",
data: formData
});
}, 1000);
}
// Usage
submitForm({
name: "John Smith",
email: "[email protected]",
studentId: "2023001"
}, function(error, result) {
if (error) {
console.error("❌ Error:", error.message);
} else {
console.log("✅", result.message);
console.log("Data:", result.data);
}
});
2. Loading Lecturer Data with Sequential Callbacks
// Simulated sequential data loading
function fetchFaculty(callback) {
setTimeout(() => {
const lecturer = [
{ id: 1, name: "Dr. Aaron", course: ["Algorithms", "Programming"] },
{ id: 2, name: "Dr. Sarah", course: ["Database", "Data Mining"] },
];
callback(null, lecturer);
}, 300);
}
function fetchSchedule(lecturerId, callback) {
setTimeout(() => {
const schedule = {
1: [{ day: "Monday", time: "08:00" }, { day: "Wednesday", time: "10:00" }],
2: [{ day: "Tuesday", time: "13:00" }, { day: "Thursday", time: "15:00" }],
};
callback(null, schedule[lecturerId] || []);
}, 300);
}
function fetchRoom(lecturerId, callback) {
setTimeout(() => {
const room = { 1: "Lab 301", 2: "Room 205" };
callback(null, room[lecturerId] || "Not determined yet");
}, 300);
}
// Sequential loading — one by one
fetchFaculty(function(error, lecturerList) {
if (error) return console.error(error);
console.log(`📋 Found ${lecturerList.length} lecturers`);
let loaded = 0;
lecturerList.forEach(function(lecturer) {
fetchSchedule(lecturer.id, function(error, schedule) {
if (error) return console.error(error);
fetchRoom(lecturer.id, function(error, room) {
if (error) return console.error(error);
console.log(`\n👤 ${lecturer.name}`);
console.log(` Mata Lecture: ${lecturer.course.join(", ")}`);
console.log(` Room: ${room}`);
schedule.forEach(function(j) {
console.log(` 📅 ${j.day} ${j.time}`);
});
loaded++;
if (loaded === lecturerList.length) {
console.log("\n✅ All lecturer data loaded successfully!");
}
});
});
});
});
3. Callbacks for Sorting and Filtering Data
const courseList = [
{ name: "Algorithms", credits: 3, semester: 2, price: 200000 },
{ name: "Web Dev", credits: 4, semester: 3, price: 250000 },
{ name: "Database", credits: 3, semester: 2, price: 200000 },
{ name: "Artificial Intelligence", credits: 3, semester: 5, price: 300000 },
{ name: "Networking", credits: 4, semester: 4, price: 250000 },
];
// Custom filter function with a callback
function filterBy(arr, callback) {
const result = [];
for (const item of arr) {
if (callback(item)) {
result.push(item);
}
}
return result;
}
// Custom sort function with a callback
function sortBy(arr, callback) {
return [...arr].sort(callback);
}
// Usage
const course3Credits = filterBy(courseList, m => m.credits === 3);
console.log("Course 3 credits:", course3Credits.map(m => m.name));
const cheapest = sortBy(courseList, (a, b) => a.price - b.price);
console.log("Cheapest:", cheapest[0].name, `$ ${cheapest[0].price}`);
const mostExpensive = sortBy(courseList, (a, b) => b.price - a.price);
console.log("Most expensive:", mostExpensive[0].name, `$ ${mostExpensive[0].price}`);
// Chaining
const courseExpensive3Credits = filterBy(
filterBy(courseList, m => m.credits === 3),
m => m.price > 200000
);
console.log("Course 3 credits mostExpensive:", courseExpensive3Credits.map(m => m.name));
Conclusion
- A callback is a function passed as an argument to another function.
- Synchronous callbacks are called immediately:
map(),filter(),forEach(). - Asynchronous callbacks are called later:
setTimeout(),fetch(),fs.readFile(). - The error-first callback is a convention:
(error, result) => {}. - Callback hell occurs when callbacks are nested too deeply — the solution is Promises and async/await.
- Callbacks are still widely used in array methods, event handlers, and legacy APIs.