JavaScriptHigher-Order Functions

Higher-Order Functions

A higher-order function is a function that either takes other functions as arguments, returns a function, or both. Because functions are values in JavaScript, this is a first-class language feature, not a workaround. Higher-order functions are how you build expressive, reusable building blocks — map, filter, reduce, event handlers, middleware, debounce, and more are all higher-order.

The two shapes
  • Takes a functionarr.map(fn) accepts a function to apply to each element.

  • Returns a functionmakeAdder(5) returns a new function add5. Often used for "configured" or "specialised" helpers.

Built-in array higher-order methods

The most common higher-order functions are right there on Array.prototype. Each one takes a function and applies it across the array in some way.

JS
const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(n => n * 2);
const evens   = numbers.filter(n => n % 2 === 0);
const total   = numbers.reduce((acc, n) => acc + n, 0);
const found   = numbers.find(n => n > 3);
const allPos  = numbers.every(n => n > 0);
const anyHuge = numbers.some(n => n > 100);

console.log({ doubled, evens, total, found, allPos, anyHuge });
{
  doubled: [ 2, 4, 6, 8, 10 ],
  evens:   [ 2, 4 ],
  total:   15,
  found:   4,
  allPos:  true,
  anyHuge: false
}
Why this is powerful — separation of concerns

Imagine writing the same logic with a for loop:

JS
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}

Compare to numbers.map(n => n * 2). The higher-order version separates what to do (multiply by 2) from how to iterate (visit each element, collect results). You stop maintaining the loop boilerplate and focus on the actual transformation.

Composing transformations

Because each method returns a value, you can chain them into clear pipelines.

JS
const users = [
  { name: "Ada", age: 36, active: true },
  { name: "Lin", age: 28, active: false },
  { name: "Pedro", age: 41, active: true },
];

const activeNames = users
  .filter(u => u.active)
  .map(u => u.name.toUpperCase());

console.log(activeNames);   // [ 'ADA', 'PEDRO' ]
Returning a function — factories

When a function returns another function, you can build specialised helpers from a general one.

JS
function makeMultiplier(factor) {
  return n => n * factor;
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(7));   // 14
console.log(triple(7));   // 21

This pattern is how bind, partial application, and currying work. The "remembered" factor lives in the closure (see Closures).

A real debounce

Debouncing — calling a function only after the user has stopped typing — is a classic higher-order pattern. It takes a function and returns a new function with extra timing logic.

JS
function debounce(fn, ms) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
}

const log = (q) => console.log("searching for:", q);
const debouncedLog = debounce(log, 300);

debouncedLog("a");
debouncedLog("ap");
debouncedLog("app");
// After 300ms of silence, prints: searching for: app
Function composition

A natural extension: write a tiny helper that takes several functions and returns a new function that runs them in sequence.

JS
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const trim   = s => s.trim();
const lower  = s => s.toLowerCase();
const dashed = s => s.replace(/\s+/g, "-");

const slug = compose(dashed, lower, trim);
console.log(slug("   Hello World  "));   // "hello-world"
Common higher-order patterns
  • map, filter, reduce — transform, narrow, summarise collections.

  • debounce / throttle — wrap a function with timing behaviour.

  • memoize — wrap a function with a cache.

  • once — wrap a function so it only runs the first time.

  • Middleware — Express, Redux, and many libraries are pipelines of higher-order functions.

  • Event handlersaddEventListener takes a function. Any time you pass a callback, you are using a higher-order API.

A productive mindset
Whenever you find yourself writing the same boilerplate around different bits of logic, ask: "Can I write this once as a higher-order function that takes the bit that changes as a parameter?" The answer is almost always yes, and the resulting code is shorter and easier to test.