What Is a Callback Function in JS: Full Tutorial
A callback is a function passed into another function to be called later. Learn synchronous and asynchronous callbacks, error-first patterns, and how callbacks power event handlers and async code.
A callback is a function passed as an argument to another function, to be called later. The receiving function decides when to call it -- immediately, after a timer, when an event fires, or when data arrives.
function greet(name) {
console.log(`Hello, ${name}!`);
}
function processUser(callback) {
const userName = "Alex";
callback(userName); // Calls the callback with the user name
}
processUser(greet); // "Hello, Alex!"The greet function is the callback. processUser is the function that receives it. greet does not run until processUser says so, and processUser controls the exact moment and the exact argument it gets called with. This separation, one function provides the timing, the other provides the behavior, is the callback pattern.
Synchronous vs Asynchronous Callbacks
Callbacks come in two forms:
A synchronous callback runs right away, before the outer function returns. An asynchronous callback is scheduled to run later, after the outer function has already returned.
| Synchronous | Asynchronous | |
|---|---|---|
| Runs | Immediately, before the outer function returns | Later, after the outer function has returned |
| Examples | map, filter, forEach | setTimeout, fetch, event listeners |
| Blocks the next line? | Yes, until the callback finishes | No, execution continues immediately |
Synchronous Callbacks
Array methods are the most common synchronous callbacks. The callback runs for each element before the method returns:
const numbers = [1, 2, 3];
// The callback (n => n * 2) runs three times, synchronously
const doubled = numbers.map(n => {
console.log(`Processing ${n}`);
return n * 2;
});
console.log(doubled);
// Processing 1
// Processing 2
// Processing 3
// [2, 4, 6]The map method does not return until every callback has finished. This is synchronous: the code runs in order, top to bottom.
Other synchronous callback examples include forEach, filter, reduce, find, some, every, and sort.
Asynchronous Callbacks
An asynchronous callback is registered now but executed later. The outer function returns immediately, and the callback runs when the event occurs:
console.log("Before setTimeout");
setTimeout(() => {
console.log("Inside the callback");
}, 1000);
console.log("After setTimeout");
// Output order:
// Before setTimeout
// After setTimeout
// Inside the callback (after 1 second)setTimeout registers the callback and returns immediately. The callback runs later, after the timer expires. The program does not pause and wait.
Event Handlers Are Callbacks
Every DOM event listener is a callback:
const button = document.querySelector("button");
button.addEventListener("click", function(event) {
console.log("Button clicked!", event.target);
});The function passed to addEventListener is a callback. It does not run when the line executes. It runs later, when the user clicks the button.
Error-First Callbacks
Node.js popularized the error-first callback convention. The first argument is always an error object, or null when nothing went wrong, and the second is the result:
function readConfig(path, callback) {
// Simulating reading a file
setTimeout(() => {
if (path === "") {
callback(new Error("Path is required"), null);
} else {
callback(null, { theme: "dark", language: "en" });
}
}, 500);
}Calling readConfig follows the same error-first shape on the receiving end: check the first argument before trusting the second one. This mirrors the convention on both sides of the call, which is exactly why it became a de facto standard across Node.js libraries:
readConfig("config.json", (err, config) => {
if (err) {
console.error("Failed:", err.message);
return;
}
console.log("Config loaded:", config);
});The pattern is always: check if the error argument exists first. If it does, handle the error and return. If not, use the result. While promises and async/await have largely replaced this pattern for new code, you will still encounter it in Node.js libraries and legacy code.
Callbacks in Custom Functions
You can write your own functions that accept callbacks:
function fetchData(url, onSuccess, onError) {
// Simulating a network request
setTimeout(() => {
if (url.startsWith("https://")) {
onSuccess({ data: "Response from " + url });
} else {
onError(new Error("Only HTTPS URLs are allowed"));
}
}, 1000);
}This variant uses two separate callbacks instead of the error-first style, one for success and one for failure, which reads clearly at the call site:
fetchData(
"https://api.example.com/users",
(data) => console.log("Success:", data),
(err) => console.error("Error:", err.message)
);This separates the fetching logic from what happens with the result. The same fetchData function could be used to load users, products, or settings, only the callbacks change.
Callback Hell and How to Avoid It
When callbacks are nested inside callbacks, the code drifts right and becomes hard to read:
// Callback hell
getUser(id, (err, user) => {
if (err) return handleError(err);
getPosts(user.id, (err, posts) => {
if (err) return handleError(err);
getComments(posts[0].id, (err, comments) => {
if (err) return handleError(err);
console.log(comments);
});
});
});Solutions:
- Name your callbacks instead of using deeply nested anonymous functions.
- Use Promises and async/await for sequential async operations.
- Modularize -- split each level into its own named function.
For the modern approach to async, see the async JavaScript articles. For the broader pattern, see Higher Order Functions in JavaScript: Full Guide. For how to pass functions as arguments, see How to Pass a Function as an Argument in JS Guide.
Rune AI
Key Insights
- A callback is a function passed as an argument to another function to be invoked later.
- Synchronous callbacks execute immediately (array methods, custom iteration).
- Asynchronous callbacks execute after an operation completes (timers, events, fetch).
- The error-first pattern (err, result) is a convention for Node.js-style callbacks.
- Callbacks power addEventListener, setTimeout, array methods, and all promise .then() handlers.
Frequently Asked Questions
What is callback hell?
Are callbacks still relevant with Promises and async/await?
What is the difference between a callback and a higher-order function?
Conclusion
A callback is a function you give to another function to call later. It is the simplest form of 'do this when that happens.' Synchronous callbacks run immediately inside array methods and custom iterators. Asynchronous callbacks wait for timers, events, or network responses. Understanding callbacks is the foundation for understanding promises, async/await, and the entire asynchronous side of JavaScript.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.