What you'll be able to do

By the end of this page you should be able to recognize a higher-order function (it takes a function, returns a function, or both), write a tiny one yourself, and understand why something like map asks for a function — without memorizing every array method yet.

Previous post: callbacks as values.

Who this is for

  • People who see .map((x) => ...) and wonder why a function is required
  • Beginners who think “higher-order” means advanced magic
  • Anyone ready to connect callbacks to a named pattern

You can skip this if you already write helpers that accept fn and return new functions on purpose. Come back when array methods feel like a foreign language.

The name is simpler than it sounds

A higher-order function is just a function that works with other functions as values — usually by taking them as arguments, returning them, or both.

You already wrote one: runTwice(fn) takes a function. That is higher-order.

function runTwice(fn) {
  fn();
  fn();
}

Scenario A — take a function. Customize behavior without rewriting the outer tool.

Scenario B — return a function. Build a specialist from a general recipe (next section).

Returning a function

function makeGreeter(prefix) {
  return function (name) {
    return prefix + ", " + name;
  };
}

const hi = makeGreeter("Hi");
const yo = makeGreeter("Yo");

console.log(hi("Asha"));
console.log(yo("Ravi"));

Scenario A — configured helpers. One factory, many greeters. Each returned function remembers prefix (a closure — we will name that more carefully later; for now: the inner function can still see prefix).

Scenario B — same idea as defaults, but reusable. Instead of repeating "Hi, " + name, you bake the prefix once.

Why map needs a function (preview)

You do not need the full arrays chapter yet. Just the shape:

const nums = [1, 2, 3];
const doubled = nums.map(function (n) {
  return n * 2;
});
console.log(doubled);

map is higher-order: it takes your function and calls it once per item. You decide how each item becomes a new value; map decides the looping.

Scenario A — double numbers. Callback returns n * 2.

Scenario B — different transform. Same map, different function — titles to lengths, prices to labels. That is why the argument must be a function: the library cannot guess your rule.

If you pass a non-function, map cannot call it and you get an error. The “function slot” is the customization point.

Higher-order vs callback (same family)

TermMeaning
CallbackThe function you pass in
Higher-order functionThe function that accepts (or returns) that function

Scenario A — runTwice(sayHi). sayHi is the callback; runTwice is higher-order.

Scenario B — nums.map(double). double is the callback; map is higher-order.

You do not need both words in every sentence — use whichever helps you remember the role.

A tiny practice file

Save as hof.html.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Higher-order</title>
  </head>
  <body>
    <button id="go">Run</button>
    <pre id="out">Result will show here.</pre>
    <script>
      function makeGreeter(prefix) {
        return function (name) {
          return prefix + ", " + name;
        };
      }

      function mapPreview(list, fn) {
        const out = [];
        for (const item of list) {
          out.push(fn(item));
        }
        return out;
      }

      document.getElementById("go").addEventListener("click", function () {
        const hi = makeGreeter("Hi");
        const lines = [];
        lines.push(hi("Asha"));
        lines.push(
          "mapPreview: " + mapPreview([1, 2, 3], function (n) {
            return n * 2;
          }).join(", "),
        );
        document.getElementById("out").textContent = lines.join("\n");
      });
    </script>
  </body>
</html>

mapPreview is a toy map: higher-order, takes fn, returns a new list. Real Array.map is built-in; this shows why it wants a function.

Mistakes I see a lot

1. Thinking higher-order means async. It is about functions as values, not about waiting.

2. Passing a value where a function is required. map(2) fails; map((n) => n * 2) works.

3. Calling the factory wrong. makeGreeter("Hi")("Asha") vs saving const hi = makeGreeter("Hi") first — both OK; know which you meant.

4. Returning nothing from the inner function when you needed a value. Same forgotten-return bug as always.

5. Overbuilding. A plain loop is fine until a higher-order helper actually clarifies the code.

What to try before the next post

  1. Write runTwice(fn) again and name it higher-order in a comment.
  2. Write makeMultiplier(factor) that returns (n) => n * factor.
  3. Run the toy mapPreview on strings to their lengths.
  4. Build the tiny HTML file.

Next in this series: pure vs impure functions — side effects in plain language, and why shared mutation bites later.

Try this next outside the series

Functions matter more once they stop being textbook examples and start shaping reusable app code.

  • Node.js async patterns — see functions return promises, accept callbacks, and coordinate real work
  • Diff Checker — compare two function versions before you keep a refactor