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.

JavaScriptbeginner
10 min read

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:

javascriptjavascript
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:

ExpressionWhat it doesReturns
greetReferences the function objectThe 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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

StyleBest forReusableStack trace
Named functionLogic used in multiple placesYesShows function name
Anonymous functionOne-time multi-line logicNoShows anonymous
Arrow functionShort inline callbacksNoShows 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

javascriptjavascript
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:

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
const numbers = [1, 2, 3, 4, 5];
 
function sum(accumulator, current) {
  return accumulator + current;
}
 
const total = numbers.reduce(sum, 0);
console.log(total); // 15

Passing 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:

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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:

javascriptjavascript
// 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

javascriptjavascript
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:

javascriptjavascript
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

javascriptjavascript
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) = 64

Pipe (Left-to-Right Composition)

javascriptjavascript
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: 64

Passing Functions: Complete Comparison Table

PatternSyntaxWhen to use
Named referencearr.map(double)Reusable, descriptive, debuggable
Anonymousarr.map(function(n) { return n * 2 })One-time, multi-statement
Arrowarr.map(n => n * 2)Short one-liners, callbacks
Bound methodsetTimeout(obj.fn.bind(obj), 100)Preserving this context
Wrapper arrowsetTimeout(() => obj.fn(), 100)Preserving this, simpler syntax
Factory resultarr.map(createMultiplier(3))Parameterized callbacks
Rune AI

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) not arr.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
RunePowered by Rune AI

Frequently Asked Questions

What does "first-class function" mean?

It means functions are treated as values in the language. They can be assigned to variables, stored in arrays or objects, passed as arguments, and returned from other functions. JavaScript, Python, and Ruby have first-class functions. Languages like C require function pointers for similar behavior.

Can I pass multiple functions as arguments?

Yes. A function can accept any number of [function arguments](/tutorials/programming-languages/javascript/js-function-parameters-vs-arguments-differences): `function process(data, transform, validate, format) { ... }`. Each parameter is independent. This pattern is common in middleware systems, data pipelines, and event processors.

What happens if I forget the parentheses and pass the result?

If you write `setTimeout(getData(), 1000)`, `getData()` runs immediately, and its return value (not the function) is passed to `setTimeout`. If `getData` returns `undefined`, `setTimeout` tries to call `undefined` after 1 second, which throws an error.

How do I pass an async function as an argument?

The same way as any other function. Async functions are regular functions that return promises: `array.map(async (item) => await process(item))`. The receiving function may or may not be aware that the callback is async. Methods like `forEach` do not await async callbacks.

Is there a limit to how many functions I can nest as arguments?

No language limit, but readability drops fast. `process(data, (x) => transform(x, (y) => validate(y, (z) => format(z))))` is hard to follow. Flatten nested callbacks using named functions, promises, or async/await for clarity.

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.