JavaScript Callback Functions

In the previous lesson, we learned how to write shorter functions with arrow syntax. Now let’s use that skill to understand callbacks, where a function is handed to another function to be run later.

🔁 What is a Callback Function?

A callback function is a function we pass as an argument to another function, so the other function can run it later. The function we pass is the callback, because the receiving function “calls it back” when the time is right.

This works because in JavaScript a function is a value, just like a number or a string. We can store it in a variable, and we can pass it into another function.

callback-functions.js
function greet() {
console.log("Hello, Alex!");
}
// We pass greet itself, without calling it
setTimeout(greet, 1000); // runs greet after 1 second

Let’s walk through what each line does:

  • function greet() { ... } defines a normal function that logs a greeting.
  • setTimeout(greet, 1000) hands greet to setTimeout as a callback. We write greet, not greet(), so we pass the function instead of running it.
  • setTimeout holds onto greet and runs it for us after one second, the 1000 milliseconds we asked for.

A function is a value

Notice we wrote greet, not greet(). The name on its own is the function value we pass along. Adding () would run it right away instead.

🧠 Why Callbacks Matter

Callbacks let one function decide what to do while another function decides when to do it. This pattern is everywhere in JavaScript.

Where We See Callbacks What the Callback Does
addEventListener Runs when the user clicks or types
setTimeout Runs after a delay
forEach, map, filter Runs once for each item in an array

Array methods and asynchronous code are built on this idea, so understanding callbacks now makes those topics much easier later.

🖱️ A Familiar Example: Button Clicks

We have likely seen a callback already when responding to a button click. The browser does not know what should happen on a click, so we give it a function to run when the click happens.

callback-functions.js
const button = document.querySelector("button");
button.addEventListener("click", function () {
console.log("The button was clicked!");
});

Let’s read this line by line:

  • document.querySelector("button") grabs the first <button> on the page and stores it in button.
  • button.addEventListener("click", ...) tells the browser to watch for clicks on that button.
  • The function () { ... } is the callback. The browser calls it for us every time the button is clicked, and we never call it ourselves.

📦 Named vs Inline Callbacks

We can pass a callback in two ways. The first is to define a named function and pass its name. The second is to write the function inline, right where it is needed, usually as an arrow function.

This first version uses a named function:

callback-functions.js
function sayHi() {
console.log("Hi, Sam!");
}
button.addEventListener("click", sayHi); // pass the name only

Here is what each line contributes:

  • function sayHi() { ... } defines the callback ahead of time and gives it a name.
  • button.addEventListener("click", sayHi) passes that name, with no (), so the browser runs sayHi on each click.

This second version writes the same callback inline as an arrow function:

callback-functions.js
button.addEventListener("click", () => {
console.log("Hi, Sam!");
});

This time we skip the separate definition and write the function right inside the call. The () => { ... } arrow function is the callback, created on the spot and handed straight to addEventListener.

Both run the same code on a click. We use a named function when the callback is reused or when we want a clear name; we use an inline arrow when the callback is short and used only once.

Pick what reads best

Short, one-off callbacks read well as inline arrows. Longer logic or callbacks used in more than one place are clearer as named functions.

⚙️ Writing a Function That Takes a Callback

Any function we write can accept a callback. The callback arrives as a normal parameter, and we run it by adding () to that parameter inside our function.

callback-functions.js
function processUser(name, callback) {
console.log("Processing " + name);
callback(name); // run the function that was passed in
}
processUser("Alex", function (name) {
console.log("Done with " + name);
});
// Processing Alex
// Done with Alex

Let’s trace the flow step by step:

  • function processUser(name, callback) declares a function whose second parameter, callback, holds whatever function we pass in.
  • console.log("Processing " + name) runs first and logs Processing Alex.
  • callback(name) runs the passed-in function and hands it the name, which logs Done with Alex.
  • The call processUser("Alex", function (name) { ... }) supplies both arguments: the name "Alex" and the callback to run.

This is exactly how built-in methods like forEach work.

🔢 A Practical Example: forEach

The array method forEach takes a callback and runs it once for every item in the array. It passes each item to our callback automatically.

callback-functions.js
const names = ["Alex", "Sam", "Jordan"];
names.forEach((name) => {
console.log("Hello, " + name);
});
// Hello, Alex
// Hello, Sam
// Hello, Jordan

Here is how forEach uses our callback:

  • const names = [...] creates the array we want to loop over.
  • names.forEach((name) => { ... }) passes an arrow function as the callback.
  • forEach calls that callback once per item, filling in name with each value in turn, so it logs a greeting for Alex, Sam, and Jordan.

We do not write a loop ourselves. We just give forEach a callback that says what to do with one name, and it runs that callback for each name in turn.

⚠️ Common Mistakes to Avoid

Mistake Problem Solution
Calling the function instead of passing it setTimeout(greet(), 1000) runs greet now and passes its result Pass the name only: setTimeout(greet, 1000)
Expecting the callback to run immediately A setTimeout callback runs later, not on the next line Put follow-up code inside the callback
Forgetting the callback receives arguments forEach passes each item, but we ignore it Add a parameter: (item) => ...

The first mistake is the most common one, so it is worth seeing side by side:

callback-functions.js
setTimeout(greet, 1000); // ✅ Correct: pass the function
setTimeout(greet(), 1000); // ❌ Wrong: runs greet right now

The difference is the ():

  • setTimeout(greet, 1000) passes the function value, so setTimeout can run it later.
  • setTimeout(greet(), 1000) runs greet immediately and passes its return value instead of the function, which is almost never what we want.

🔧 Try It Yourself!

  1. Write a named function that logs a message, then pass it to setTimeout so it runs after one second.
  2. Add a click listener to a button using an inline arrow function as the callback.
  3. Write your own function that takes a callback parameter and runs it with callback().
  4. Use forEach on an array of names and log a greeting for each one.

🧩 What You’ve Learned

  • ✅ A callback is a function passed to another function to be run later
  • ✅ We pass a function by its name, without the ()
  • ✅ Callbacks power addEventListener, setTimeout, and array methods
  • ✅ A callback can be a named function or an inline arrow function
  • ✅ Any function we write can accept and run a callback

🚀 What’s Next?

Now that we understand callbacks, we are ready for the arrays that array methods work on. Let’s continue to Creating Arrays.

Share & Connect