How to Pass a Function as an Argument in JS Guide
Learn how to pass functions as arguments in JavaScript. Covers first-class functions, named and anonymous callbacks, arrow function arguments, higher-order function patterns, and practical examples with array methods, event handlers, and custom processors.
JavaScript treats functions as first-class citizens. This means a function can be stored in a variable, returned from another function, and passed as an argument to another function. Passing functions as arguments is the foundation of callbacks, event handling, and array processing in JavaScript. Once you understand this pattern, the entire ecosystem of map, filter, forEach, event listeners, and promise chains starts to make sense.
This tutorial explains how and why you pass functions as arguments, covers the different ways to do it (named functions, anonymous functions, arrow functions), and builds practical examples from simple to advanced.
Why Functions Can Be Passed as Arguments
In JavaScript, functions are values. They are objects that can be assigned, stored, and passed around just like numbers or strings:
const greet = function (name) {
return `Hello, ${name}!`;
};
console.log(typeof greet); // "function"
console.log(greet); // [Function: greet]
console.log(greet("Alice")); // "Hello, Alice!"The difference between greet (the function reference) and greet("Alice") (calling the function) is critical:
| Expression | What it does | Returns |
|---|---|---|
greet | References the function object | The function itself |
greet("Alice") | Calls the function with an argument | "Hello, Alice!" |
When you pass a function as an argument, you pass the reference (without parentheses), not the result.
Basic Pattern: Passing a Named Function
function shout(message) {
return message.toUpperCase() + "!";
}
function whisper(message) {
return message.toLowerCase() + "...";
}
function deliver(message, formatter) {
console.log(formatter(message));
}
deliver("Hello World", shout); // "HELLO WORLD!"
deliver("Hello World", whisper); // "hello world..."The deliver function accepts another function as its second argument and calls it with the message. You can pass any function that accepts a string and returns a string.
Three Ways to Pass Functions
1. Named Function Reference
function double(n) {
return n * 2;
}
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(double);
console.log(doubled); // [2, 4, 6, 8, 10]2. Anonymous Function Expression
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function (n) {
return n * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]3. Arrow Function
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]When to Use Each Style
| Style | Best for | Reusable | Stack trace |
|---|---|---|---|
| Named function | Logic used in multiple places | Yes | Shows function name |
| Anonymous function | One-time multi-line logic | No | Shows anonymous |
| Arrow function | Short inline callbacks | No | Shows anonymous |
Do Not Call the Function When Passing
Write numbers.map(double) not numbers.map(double()). Adding parentheses calls the function immediately and passes its return value, not the function itself. This is the most common mistake beginners make.
Common Mistake: Calling Instead of Passing
function logMessage() {
console.log("Button clicked!");
}
// WRONG: calls logMessage immediately, passes undefined to addEventListener
document.getElementById("btn").addEventListener("click", logMessage());
// CORRECT: passes the function reference
document.getElementById("btn").addEventListener("click", logMessage);The same mistake with setTimeout:
function sayHello() {
console.log("Hello!");
}
// WRONG: runs immediately, setTimeout receives undefined
setTimeout(sayHello(), 1000);
// CORRECT: runs after 1 second
setTimeout(sayHello, 1000);Passing Functions to Array Methods
Array methods are the most common place where you pass functions as arguments:
map: Transform Each Element
const prices = [10, 20, 30, 40];
function addTax(price) {
return price * 1.08;
}
const withTax = prices.map(addTax);
console.log(withTax); // [10.8, 21.6, 32.4, 43.2]filter: Keep Matching Elements
const ages = [12, 18, 25, 16, 30, 14];
function isAdult(age) {
return age >= 18;
}
const adults = ages.filter(isAdult);
console.log(adults); // [18, 25, 30]sort: Custom Comparison
const names = ["Charlie", "Alice", "Bob"];
function alphabetical(a, b) {
return a.localeCompare(b);
}
names.sort(alphabetical);
console.log(names); // ["Alice", "Bob", "Charlie"]reduce: Accumulate a Result
const numbers = [1, 2, 3, 4, 5];
function sum(accumulator, current) {
return accumulator + current;
}
const total = numbers.reduce(sum, 0);
console.log(total); // 15Passing Functions with Extra Arguments
Sometimes you need to pass a function that uses extra data beyond what the calling function provides. Use a wrapper function:
function multiply(factor, number) {
return number * factor;
}
const numbers = [1, 2, 3, 4];
// Can't pass multiply directly to map (wrong signature)
// This wraps it to capture the factor:
const tripled = numbers.map((n) => multiply(3, n));
console.log(tripled); // [3, 6, 9, 12]Factory Functions for Reusable Callbacks
function createMultiplier(factor) {
return function (number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.map(double)); // [2, 4, 6, 8, 10]
console.log(numbers.map(triple)); // [3, 6, 9, 12, 15]Building Custom Functions That Accept Functions
Data Processor
function processData(data, transformer, validator) {
const results = [];
for (const item of data) {
const transformed = transformer(item);
if (validator(transformed)) {
results.push(transformed);
}
}
return results;
}
const rawNumbers = [1, -2, 3, -4, 5, -6];
const positiveDoubles = processData(
rawNumbers,
(n) => n * 2, // transformer
(n) => n > 0 // validator
);
console.log(positiveDoubles); // [2, 6, 10]Retry with Custom Logic
function retry(operation, maxAttempts, shouldRetry) {
let attempt = 0;
while (attempt < maxAttempts) {
attempt++;
try {
return operation();
} catch (error) {
if (attempt >= maxAttempts || !shouldRetry(error)) {
throw error;
}
console.log(`Attempt ${attempt} failed, retrying...`);
}
}
}
// Usage: retry an operation up to 3 times for network errors only
retry(
() => fetchData("/api/users"),
3,
(err) => err.message.includes("network")
);Timer with Callback
function measureTime(fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`Execution time: ${(end - start).toFixed(2)}ms`);
return result;
}
const result = measureTime(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
});Event Handlers: Functions as Arguments
Event listeners are one of the most common uses of passing functions:
// Named function
function handleClick(event) {
console.log("Clicked:", event.target.id);
}
document.getElementById("btn").addEventListener("click", handleClick);
// Arrow function (inline)
document.getElementById("btn").addEventListener("click", (event) => {
console.log("Clicked:", event.target.id);
});Multiple Handlers from a Factory
function createLogger(eventType) {
return function (event) {
console.log(`[${eventType}] Target: ${event.target.tagName}`);
};
}
const button = document.getElementById("btn");
button.addEventListener("click", createLogger("CLICK"));
button.addEventListener("mouseover", createLogger("HOVER"));Passing Methods as Arguments (The this Problem)
When passing an object method as an argument, it loses its this context:
const user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
},
};
// PROBLEM: this is undefined (or window in non-strict mode)
setTimeout(user.greet, 1000); // "Hello, I'm undefined"
// FIX 1: wrap in arrow function
setTimeout(() => user.greet(), 1000); // "Hello, I'm Alice"
// FIX 2: bind
setTimeout(user.greet.bind(user), 1000); // "Hello, I'm Alice"Methods Lose Context
When you detach a method from its object (by passing it as a callback), this no longer points to the object. Always wrap in an arrow function or use .bind() to preserve the context.
Composition: Combining Passed Functions
function compose(...fns) {
return function (value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
function addOne(n) { return n + 1; }
function double(n) { return n * 2; }
function square(n) { return n * n; }
const transform = compose(square, double, addOne);
// Reads right to left: addOne -> double -> square
console.log(transform(3)); // square(double(addOne(3))) = square(double(4)) = square(8) = 64Pipe (Left-to-Right Composition)
function pipe(...fns) {
return function (value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
const process = pipe(addOne, double, square);
// Reads left to right: addOne -> double -> square
console.log(process(3)); // same result: 64Passing Functions: Complete Comparison Table
| Pattern | Syntax | When to use |
|---|---|---|
| Named reference | arr.map(double) | Reusable, descriptive, debuggable |
| Anonymous | arr.map(function(n) { return n * 2 }) | One-time, multi-statement |
| Arrow | arr.map(n => n * 2) | Short one-liners, callbacks |
| Bound method | setTimeout(obj.fn.bind(obj), 100) | Preserving this context |
| Wrapper arrow | setTimeout(() => obj.fn(), 100) | Preserving this, simpler syntax |
| Factory result | arr.map(createMultiplier(3)) | Parameterized callbacks |
Rune AI
Key Insights
- Functions are values: they can be passed as arguments just like strings or numbers
- Pass references, not calls:
arr.map(double)notarr.map(double()) - Three styles: named functions for reuse, anonymous for one-time logic, arrows for short callbacks
- Object methods lose
this: use arrow wrappers or.bind()when passing methods as callbacks - Factory functions create parameterized callbacks:
createMultiplier(3)returns a ready-to-use function
Frequently Asked Questions
What does "first-class function" mean?
Can I pass multiple functions as arguments?
What happens if I forget the parentheses and pass the result?
How do I pass an async function as an argument?
Is there a limit to how many functions I can nest as arguments?
Conclusion
Passing functions as arguments is one of JavaScript's most powerful patterns. It enables callbacks, event handling, array transformations, and functional composition. Pass function references (without parentheses) to let the receiving function decide when to call them. Use named functions for reusable, debuggable callbacks. Use arrow functions for short inline logic. Watch for the this context problem when passing object methods, and fix it with arrow wrappers or .bind(). Once you internalize this pattern, higher-order functions like map, filter, reduce, and sort become natural tools in your JavaScript toolkit.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.