AlgoInsight

Free lesson · JavaScript Basic to Advance

Higher-Order Functions

Easy5 min read

Functions That Work With Other Functions

A higher-order function is a function that does one of two things: it takes another function as a parameter, or it returns a function. Either of those qualifies. Many JavaScript functions do both.

// Takes a function as a parameter
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(n) {
  return n * 2;
});
console.log(doubled);

// Returns a function
function makeMultiplier(factor) {
  return function(n) {
    return n * factor;
  };
}

const triple = makeMultiplier(3);
console.log(triple(5));
console.log(triple(10));

Both map and makeMultiplier are higher-order functions. map takes a function and applies it to each element. makeMultiplier returns a customized function based on the factor you give it.

I'm Nikhil. Topics 5.6 and 5.7 are mine because this is where the JavaScript conceptual shift happens — from "functions are blocks of code I run" to "functions are values I move around." Once you internalize this shift, everything in the rest of the course gets easier. Closures (Module 6), promises (Module 9), the entire functional style of modern JavaScript — all of it depends on understanding that functions can be passed, stored, and returned just like any other value.

This Topic and the next are the most conceptually important Topics in Module 5. Spend time here.

Why Higher-Order Functions Exist

Higher-order functions let you write code that's flexible about what it does, not just with what data.

Without higher-order functions, every operation you wanted to do on an array would need its own function:

// Without higher-order functions, you'd need a separate function per operation
function doubleAll(numbers) {
  const result = [];
  for (const n of numbers) {
    result.push(n * 2);
  }
  return result;
}

function squareAll(numbers) {
  const result = [];
  for (const n of numbers) {
    result.push(n * n);
  }
  return result;
}

console.log(doubleAll([1, 2, 3]));
console.log(squareAll([1, 2, 3]));

Notice the pattern: both functions are identical except for the operation inside the loop. The repetition is wasteful — every new operation needs its own duplicated function.

With map, the operation becomes a parameter:

function transform(numbers, operation) {
  const result = [];
  for (const n of numbers) {
    result.push(operation(n));
  }
  return result;
}

console.log(transform([1, 2, 3], n => n * 2));     // [2, 4, 6]
console.log(transform([1, 2, 3], n => n * n));     // [1, 4, 9]
console.log(transform([1, 2, 3], n => n + 100));   // [101, 102, 103]

One transform function. Infinitely many operations. That's what higher-order functions buy you: code that varies in behavior without varying in structure.

Array.prototype.map is the built-in version of this pattern. Same idea, used constantly in real JavaScript code.

Interactive animation: The built-in versions of this pattern - map transforms, filter selects and reduce accumulates, each driven by a callback.

Callbacks

A callback is a function passed to another function with the expectation that it will be called at some point. The receiving function controls when (and whether) the callback runs.

function processWithLogging(value, callback) {
  console.log(`Processing ${value}...`);
  const result = callback(value);
  console.log(`Done. Result: ${result}`);
  return result;
}

processWithLogging(5, n => n * 2);
processWithLogging("hello", s => s.toUpperCase());

processWithLogging is a higher-order function (takes a function as parameter). The function it takes is a callback — it gets called inside processWithLogging's body.

Callbacks are the foundation of how JavaScript handles many things:

In each case, the receiving function controls when the callback gets called. You're delegating "what to do" while the receiving function handles "when to do it."

graph LR
    A[Your code] -->|passes callback| B[Higher-Order Function]
    B -->|calls callback| C[Your callback runs]
    C -->|returns value| B
    B -->|returns result| A

    style B fill:#7c3aed,color:#fff
This diagram shows the relationship between your code and a higher-order function with a callback. Your code passes a callback function as an argument to the higher-order function. The higher-order function decides when and how to call your callback. When the callback runs, it returns a value back to the higher-order function. The higher-order function then returns a final result back to your code. The key insight is that you're delegating control of when the callback runs while keeping control of what it does.

Built-in Higher-Order Methods on Arrays

JavaScript arrays come with several higher-order methods. These are the ones you'll use constantly:

forEach — visit each element (no return value):

const founders = ["Olena", "Nikhil", "Emeka"];
founders.forEach(name => console.log(`Founder: ${name}`));

map — transform each element into a new array:

const numbers = [1, 2, 3, 4];
const squared = numbers.map(n => n * n);
console.log(squared);

filter — keep elements that match a predicate:

const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens);

reduce — combine all elements into a single value:

const numbers = [1, 2, 3, 4, 5];
const total = numbers.reduce((sum, n) => sum + n, 0);
console.log(total);

find — return the first element matching a predicate (or undefined):

const users = [{age: 25}, {age: 30}, {age: 18}];
const minor = users.find(u => u.age < 21);
console.log(minor);

some and every — test whether some/all elements match a predicate:

const ages = [25, 30, 18, 40];
console.log(ages.some(a => a < 21));    // true — at least one
console.log(ages.every(a => a < 21));   // false — not all

sort — sort in place, using a comparison callback:

const numbers = [3, 1, 4, 1, 5, 9, 2, 6];
numbers.sort((a, b) => a - b);
console.log(numbers);

Each of these is a higher-order function: it takes a function (the callback) and uses it to determine how to operate on the array.

Interactive animation: reduce traced step by step - the sum starts at the seed 0 and each callback return becomes the next sum.

Functions That Return Functions

The other half of higher-order functions: functions that return functions. This is one of the most powerful patterns in JavaScript and the gateway to closures (Module 6).

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

const addFive = makeAdder(5);
const addTen = makeAdder(10);

console.log(addFive(3));   // 8
console.log(addFive(100)); // 105
console.log(addTen(3));    // 13

makeAdder returns a customized function. Each call to makeAdder produces a new function — one that "remembers" the value of x it was created with.

This is the closure mechanism (covered in Module 6). The returned function has access to x from the enclosing scope, even after makeAdder has finished running. That memory of an enclosing scope is what defines a closure.

Real-world uses of "functions that return functions":

Configuration helpers — create a customized utility once, use it many times:

function makeValidator(minLength, maxLength) {
  return function(value) {
    return value.length >= minLength && value.length <= maxLength;
  };
}

const isShortName = makeValidator(2, 20);
const isLongDescription = makeValidator(50, 500);

console.log(isShortName("Olena"));         // true
console.log(isShortName("O"));             // false
console.log(isLongDescription("Hello"));   // false

Partial application — fix some arguments now, fill in the rest later:

function multiply(a, b) {
  return a * b;
}

function partial(fn, fixedArg) {
  return function(remainingArg) {
    return fn(fixedArg, remainingArg);
  };
}

const triple = partial(multiply, 3);
const double = partial(multiply, 2);

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

Both triple and double are derived from multiply. We've created specialized functions from a general one by fixing one of its arguments. This is a core technique in functional programming.

Interactive animation: The factory pattern - each call returns a new function that remembers the value it was configured with.

Common Pitfalls

Warning: The higher-order function traps.

Calling a function when you meant to pass it. Writing setTimeout(myFunction(), 1000) calls myFunction immediately and passes whatever it returned. You meant setTimeout(myFunction, 1000) to pass the function itself so setTimeout can call it later. This is one of the most common bugs with callbacks.

Forgetting that callbacks aren't called immediately. When you write button.addEventListener("click", handleClick), handleClick is not called at that line. It's stored, then called later when a click happens. New JavaScript developers often try to debug callbacks by adding console.log next to where they pass them — but that runs before the callback runs.

Returning the wrong thing from a callback. Many higher-order methods depend on what your callback returns. filter expects a boolean. map uses the return value as the transformed element. sort expects a number. Returning the wrong type produces silently incorrect results.

Why This Matters in Interviews

Interview: Higher-order functions are central to modern JavaScript and central to JavaScript interviews. Many problems are most cleanly solved using map, filter, reduce. Interviewers watch whether you reach for these naturally or whether you write everything as classic for loops.

"Implement your own map" is a classic intermediate interview question — it tests whether you understand how map works internally and whether you can reason about callbacks.

Topic 5.8 — Interview Lens: Functions and Higher-Order Patterns — covers the deeper interview treatment of these patterns, including the "implement this method" challenges.

Try It

Exercise: Implement your own version of map from scratch, called myMap, that takes an array and a callback function and returns a new array with the callback applied to each element. Don't use the built-in map. Use a for or for...of loop.

function myMap(array, callback) {
  // your implementation here
}

console.log(myMap([1, 2, 3], n => n * 2));   // should print [2, 4, 6]
console.log(myMap(["a", "b"], s => s.toUpperCase()));   // should print ["A", "B"]

Once you've got that working, try implementing myFilter (returns elements where callback returns true) and myReduce (combines all elements using an accumulator). Building these from scratch is the best way to internalize how higher-order functions actually work.

Back to JavaScript Basic to Advance