Functions as Arguments
In the previous lesson, you learned that a higher-order function is a function that accepts another function, returns another function, or both.
Now let's zoom in on the first part:
Passing functions as arguments.This is one of the most common JavaScript patterns.
You will see it in:
- event listeners
- timers
- array methods
- async code
- reusable utilities
- framework code
When you pass a function into another function, the passed function is usually called a callback.
Functions Are Values
In JavaScript, functions are values.
That means you can store a function in a variable:
function greet(name) {
return `Hello, ${name}!`;
}
const greetingFunction = greet;
console.log(greetingFunction("Alice")); // Hello, Alice!Notice this line:
const greetingFunction = greet;There are no parentheses after greet.
That means JavaScript stores the function itself in greetingFunction.
Because functions are values, they can also be passed as arguments.
What Is a Callback?
A callback is a function passed as an argument to another function.
The receiving function can call the callback whenever it needs to.
Example:
function greet(name) {
console.log(`Hello, ${name}!`);
}
function processUser(name, callback) {
console.log("Processing user...");
callback(name);
}
processUser("Alice", greet);Output:
Processing user...
Hello, Alice!Here is what happens:
greetis defined as a normal function.processUseraccepts anameand acallback.processUser("Alice", greet)passes thegreetfunction intoprocessUser.- Inside
processUser,callback(name)runs the passed function.
The callback parameter receives the function:
callbackThen this line calls it:
callback(name);Passing the Function, Not Calling It
This is one of the most important callback rules:
Pass the function reference, not the result of calling the function.Correct:
processUser("Alice", greet);Incorrect:
processUser("Alice", greet("Alice"));The difference is:
| Code | Meaning |
|---|---|
greet |
Pass the function itself |
greet() |
Call the function now |
greet("Alice") |
Call the function now with "Alice" |
If you write this:
processUser("Alice", greet("Alice"));JavaScript runs greet("Alice") immediately.
If greet does not return another function, the second argument becomes its return value.
In this example, greet logs a message but returns undefined.
So JavaScript effectively does this:
processUser("Alice", undefined);Then processUser tries to call:
callback(name);But callback is undefined, so the code fails.
Callback Parameters
A callback can receive arguments from the higher-order function.
function processUser(name, callback) {
callback(name);
}
function greet(name) {
console.log(`Hello, ${name}!`);
}
processUser("Mira", greet); // Hello, Mira!The processUser function decides what to pass into the callback.
In this case, it passes name.
You can pass more than one value:
function calculate(a, b, operation) {
return operation(a, b);
}
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
console.log(calculate(3, 4, add)); // 7
console.log(calculate(3, 4, multiply)); // 12The same higher-order function can perform different behavior depending on the callback.
Anonymous Callback Functions
You do not always need to define the callback separately.
You can pass a function directly as an argument.
function processUser(name, callback) {
callback(name);
}
processUser("Alice", function (name) {
console.log(`Hello, ${name}!`);
});This function:
function (name) {
console.log(`Hello, ${name}!`);
}is anonymous because it has no name.
Anonymous callbacks are useful for short, one-time behavior.
Arrow Function Callbacks
Modern JavaScript often uses arrow functions for short callbacks.
function processUser(name, callback) {
callback(name);
}
processUser("Alice", (name) => {
console.log(`Hello, ${name}!`);
});For a single expression, you may see a shorter form:
processUser("Alice", (name) => console.log(`Hello, ${name}!`));Arrow callbacks are common because they are concise.
You will see them constantly with array methods.
Named Callbacks vs Inline Callbacks
Both styles are valid.
Named callback:
function isAdult(age) {
return age >= 18;
}
const ages = [12, 18, 24, 15];
const adults = ages.filter(isAdult);
console.log(adults); // [18, 24]Inline callback:
const ages = [12, 18, 24, 15];
const adults = ages.filter((age) => age >= 18);
console.log(adults); // [18, 24]Use an inline callback when the logic is short and obvious.
Use a named callback when the logic is longer, reused, or deserves a clear name.
Array Methods Use Callbacks
Array methods are some of the most common places to pass functions as arguments.
forEach
forEach runs a callback for each item.
const numbers = [1, 2, 3, 4];
numbers.forEach(function (number) {
console.log(number * 2);
});Output:
2
4
6
8With an arrow function:
numbers.forEach((number) => {
console.log(number * 2);
});The callback receives each item one at a time.
map Transforms Values
map uses a callback to transform each item into a new value.
const numbers = [1, 2, 3];
const doubled = numbers.map((number) => {
return number * 2;
});
console.log(doubled); // [2, 4, 6]With concise arrow syntax:
const doubled = numbers.map((number) => number * 2);The callback returns the new value for each item.
map returns a new array.
filter Keeps or Removes Values
filter uses a callback to decide which items should stay.
const ages = [15, 22, 18, 30, 12];
const adults = ages.filter((age) => {
return age >= 18;
});
console.log(adults); // [22, 18, 30]The callback should return:
trueto keep the itemfalseto remove the item
Short version:
const adults = ages.filter((age) => age >= 18);reduce Combines Values
reduce uses a callback to combine array values into one result.
const numbers = [1, 2, 3, 4];
const total = numbers.reduce((sum, number) => {
return sum + number;
}, 0);
console.log(total); // 10The callback receives:
- the current accumulated value
- the current item
In this example, sum starts at 0.
Then each number is added to it.
Event Listeners Use Callbacks
Browser event listeners are built around callbacks.
const button = document.getElementById("myButton");
button.addEventListener("click", function (event) {
console.log("Button was clicked!");
console.log(event);
});You pass a function to addEventListener.
The browser stores that function and calls it later when the button is clicked.
The callback receives an event object with information about what happened.
With an arrow function:
button.addEventListener("click", (event) => {
console.log("Button was clicked!");
console.log(event);
});Timers Use Callbacks
Timers also accept functions as arguments.
setTimeout(() => {
console.log("This runs after 2 seconds");
}, 2000);The first argument is the callback.
The second argument is the delay in milliseconds.
This code does not pause the entire program.
It schedules the callback to run later.
Synchronous Callbacks
Some callbacks run immediately during the current operation.
const numbers = [1, 2, 3];
numbers.forEach((number) => {
console.log(number);
});
console.log("Done");Output:
1
2
3
DoneThe forEach callback is synchronous.
It runs before the next line after forEach.
Asynchronous Callbacks
Some callbacks run later.
setTimeout(() => {
console.log("Timer finished");
}, 1000);
console.log("Waiting");Output:
Waiting
Timer finishedThe timer callback is asynchronous.
It runs after the current code finishes and the delay has passed.
Callbacks are a foundation for asynchronous JavaScript.
You will study async behavior more deeply later.
Custom Callback Example
You can write your own functions that accept callbacks.
function repeat(times, action) {
for (let i = 0; i < times; i++) {
action(i);
}
}
repeat(3, (index) => {
console.log(`Run ${index + 1}`);
});Output:
Run 1
Run 2
Run 3The repeat function controls the loop.
The callback controls what happens on each iteration.
This separation makes the function reusable.
Callback Return Values
Sometimes the higher-order function uses the callback's return value.
Example:
function transform(value, callback) {
return callback(value);
}
const result = transform(5, (number) => {
return number * 2;
});
console.log(result); // 10The callback returns 10.
Then transform returns that value.
Array methods like map, filter, and reduce depend on callback return values.
Other APIs, like forEach and many event listeners, usually ignore callback return values.
Naming Callback Parameters
Good callback parameter names make your function easier to understand.
Generic:
function process(value, fn) {
return fn(value);
}Clearer:
function processUser(user, onComplete) {
return onComplete(user);
}Useful names include:
callbackhandlerlistenerpredicatetransformonSuccessonErroronComplete
Choose a name that explains when or why the callback will run.
Callback and Closure Together
Callbacks often use closures.
function setupCounterButton(button) {
let count = 0;
button.addEventListener("click", () => {
count++;
console.log(`Clicked ${count} times`);
});
}The callback passed to addEventListener closes over count.
Every time the button is clicked, the callback updates the same preserved variable.
This combines two important ideas:
- functions as arguments
- closures
Best Practices
Pass a function reference when the receiving function should call it later:
processUser("Alice", greet);Use inline callbacks for short logic:
const names = users.map((user) => user.name);Extract longer callbacks into named functions:
function hasValidEmail(user) {
return user.email.includes("@");
}
const validUsers = users.filter(hasValidEmail);Use clear callback parameter names:
function saveUser(user, onSuccess) {
// save user
onSuccess(user);
}Remember whether the API uses the callback's return value.
For example, filter needs a return value:
const adults = ages.filter((age) => age >= 18);But forEach is usually used for side effects:
ages.forEach((age) => {
console.log(age);
});Common Mistakes
Mistake 1: Calling the Callback Instead of Passing It
function greet(name) {
console.log(`Hello, ${name}`);
}
function processUser(name, callback) {
callback(name);
}
processUser("Alice", greet("Alice"));This calls greet("Alice") immediately.
Correct version:
processUser("Alice", greet);Mistake 2: Forgetting to Call the Callback
function processUser(name, callback) {
console.log("Processing user...");
}
processUser("Alice", (name) => {
console.log(`Hello, ${name}`);
});The callback is passed in, but never called.
Correct version:
function processUser(name, callback) {
console.log("Processing user...");
callback(name);
}Mistake 3: Forgetting return in a filter Callback
const ages = [15, 22, 18];
const adults = ages.filter((age) => {
age >= 18;
});
console.log(adults); // []With braces, an arrow function needs an explicit return.
Correct version:
const adults = ages.filter((age) => {
return age >= 18;
});Or:
const adults = ages.filter((age) => age >= 18);Mistake 4: Making Inline Callbacks Too Complex
const result = data.map((item) => {
// many lines of logic
});If the callback grows, extract it:
function transformItem(item) {
// many lines of logic
}
const result = data.map(transformItem);Mistake 5: Assuming Every Callback Is Async
[1, 2, 3].forEach((number) => {
console.log(number);
});
console.log("Done");The callback runs synchronously.
Not all callbacks are async.
Timers and event listeners run later, but array callbacks usually run immediately.
Quick Check
What does this code log?
function run(callback) {
callback();
}
function sayHi() {
console.log("Hi");
}
run(sayHi);It logs:
HisayHi is passed as a function and called inside run.
What is wrong here?
run(sayHi());It calls sayHi immediately and passes its return value to run.
To pass the function itself, use:
run(sayHi);What does this return?
const numbers = [1, 2, 3];
const doubled = numbers.map((number) => number * 2);It returns:
[2, 4, 6]The callback tells map how to transform each number.
Summary
Passing functions as arguments is one of the most important JavaScript patterns.
- A callback is a function passed into another function.
- Pass a function reference like
greet, not a function call likegreet(). - The receiving function decides when to call the callback.
- Callbacks can be named functions, anonymous functions, or arrow functions.
- Array methods, event listeners, and timers all use callbacks.
- Some callbacks are synchronous, while others run later asynchronously.
- Callback return values matter for methods like
map,filter, andreduce. - Long callbacks are often clearer when extracted into named functions.