JavaScript Callback Functions
Table of Contents + −
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.
function greet() { console.log("Hello, Alex!");}
// We pass greet itself, without calling itsetTimeout(greet, 1000); // runs greet after 1 secondLet’s walk through what each line does:
function greet() { ... }defines a normal function that logs a greeting.setTimeout(greet, 1000)handsgreettosetTimeoutas a callback. We writegreet, notgreet(), so we pass the function instead of running it.setTimeoutholds ontogreetand runs it for us after one second, the1000milliseconds 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.
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 inbutton.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:
function sayHi() { console.log("Hi, Sam!");}
button.addEventListener("click", sayHi); // pass the name onlyHere 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 runssayHion each click.
This second version writes the same callback inline as an arrow function:
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.
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 AlexLet’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 logsProcessing Alex.callback(name)runs the passed-in function and hands it the name, which logsDone 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.
const names = ["Alex", "Sam", "Jordan"];
names.forEach((name) => { console.log("Hello, " + name);});
// Hello, Alex// Hello, Sam// Hello, JordanHere 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.forEachcalls that callback once per item, filling innamewith each value in turn, so it logs a greeting forAlex,Sam, andJordan.
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:
setTimeout(greet, 1000); // ✅ Correct: pass the functionsetTimeout(greet(), 1000); // ❌ Wrong: runs greet right nowThe difference is the ():
setTimeout(greet, 1000)passes the function value, sosetTimeoutcan run it later.setTimeout(greet(), 1000)runsgreetimmediately and passes its return value instead of the function, which is almost never what we want.
🔧 Try It Yourself!
- Write a named function that logs a message, then pass it to
setTimeoutso it runs after one second. - Add a click listener to a button using an inline arrow function as the callback.
- Write your own function that takes a
callbackparameter and runs it withcallback(). - Use
forEachon 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.