What is a callback in JavaScript? (With simple examples)

Callback functions are incredibly common in JavaScript. If you've read anything beyond very simple code, there's a strong chance it used callbacks.

So, what are they?

In JavaScript, a callback is a function that isn't immediately executed, but is instead passed to another function as a parameter. It is then executedβ€”or 'called back'β€”at a later point, in the body of the containing function.

Callbacks can be a little tricky to get your head around at first, but this article will break it down in simple terms. By the end, you'll have a solid understanding of what JavaScript callbacks are, how they work, and what they're used for in the real world.

In this article

How do callback functions work?

To grasp callbacks, you first have to understand that functions in JavaScript are objects. This means they can be stored in variables, passed as arguments to functions, created within functions, and returned from functionsβ€”just like any other object can. Because we can treat them like this, we say that they are first-class objects.

Consider the following example, in which the callback parameter is a function:

// The callback parameter is a function
const sumNumbers = function(num1, num2, callback) {
// Add the first two parameters
const result = num1 + num2;
// Call the callback function with the result
callback(result);
}

At this point, you might be wondering how this works exactly.

We've defined callback as the third parameter, and called it inside the sumNumbers function body, but how do we actually pass a function as the callback parameter?

// Call the sumNumbers function like this
sumNumbers(2, 4, function(number) {
console.log("Yay, callback called! And the result was " + number);
});

See? The third parameter, defined earlier as callback, is in fact a function.

We're passing it here as an anonymous function (that is, a function without a name), but it's worth mentioning that we could just as easily pass a named function too; there's no difference. Writing it inline like this just makes for slightly shorter code examples.

We've seen two things so far: a function calling another function (sumNumbers calling callback) and a function being passed into another function as a parameter.

What are some real-world examples of callback functions?

A trivial example is enough to demonstrate the basic concept, but what about more complex use cases? What kind of things do we use callbacks for in real code?

Event handling

If you're working with client-side JavaScript, you'll often use event listeners.

The addEventListener() method sets up a callback function that will be called whenever the specified event is delivered to the target.

Here's an example from MDN which sets up an event listener to watch for mouse clicks on an element:

<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
// Function to change the content of t2
function modifyText(new_text) {
const t2 = document.getElementById("t2");
t2.firstChild.nodeValue = new_text;
}

// Add event listener to table with an arrow function
const el = document.getElementById("outside");
el.addEventListener("click", () => {
modifyText("four");
});

The event listener is an anonymous function. When it's triggered by the click event, it then calls the modifyText() function, which is responsible for actually responding to the event.

Unit testing

If you've done any JavaScript unit testing, you've likely seen callbacks here as well.

This example is from Jest, a popular JavaScript testing library:

// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
// sum.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});

Notice the second parameter being passed to the test() function in the test file.

You might not have thought about it, but that is actually an anonymous callback function, which is executed by the test framework when you run your tests.

Asynchronous operations

In web programming, a lot of things we need to do are asynchronous (or 'async'). Things like fetching data from our app's backend, interacting with an external API, or submitting a form are all asynchronous operations.

Because these things take an indeterminate amount of time, JavaScript won't wait for the response before continuing to execute; instead it's up to us to define how to handle that response whenever it does come back.

One way to do this is with callbacks.

This is the core idea behind AJAX, which is a technique in web development which involves making requests to a server directly from JavaScript running in the browser. It allows us to fetch new data without reloading the page, which creates a better user experience.

Let's look at callbacks in the context of an AJAX request using jQuery:

const getData = function(item, callback) {
// Trigger AJAX request to fetch the data
$.ajax({
url: "/some/path/" + item,
method: "GET",
success: function(data) {
// Call the callback function now we have the data
callback(data);
}
});
};

// Define a callback function to handle the response data
const appendItem = function(data) {
$("element").append(data);
};

getData("item", appendItem);

Asynchronous operations are one of the most common real-world situations where you'll see callback functions.

What are the problems with callback functions?

If you have several async operations to complete, one after the other, it's possible to end up in something known to JavaScript programmers as 'callback hell'.

This happens when you have multiple nested callbacks, causing a deeply indented pyramid shape in your code, like this:

getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
getMoreData(d, function(e) {
...
});
});
});
});
});

It's hard to read, hard to test, hard to work with, and all around just generally best avoided.

Tips for avoiding callback hell

  • Define and name your functions at the top level: This way, each function will only need to take a single callback (so you'll avoid nesting), and descriptive function names will give your code better clarity and readability.
  • Use promises: Promises are objects returned from async functions, which are essentially placeholders for an eventual future value. You attach callbacks to them, instead of passing callbacks into a function.
  • Use async/await: This is a newer technique which is actually just a simpler syntax for working with promises. It lets you write async code as if it were synchronous, removing a lot of the need for callbacks at all.

Succeed in tech

Get actionable tips on coding, getting hired and working as a developer.

I won't spam you. Unsubscribe any time.