Higher-Order Functions & Callbacks

Intermediate · 18 min read · ▶ live playground · ✦ checkpoint

Sometimes the most useful thing you can pass into a function is another function. A callback is a function you hand to another function so that function can call it at the right moment, like handing a recipe card to a teammate and saying, "use this step when you get there."

Functions can receive functions

A higher-order function is a function that takes another function as an argument, returns a function, or both. You already know functions are values; now you use that on purpose.

function printMood(playerName, mood, formatMood) {
  const finalMood = formatMood(mood);
  console.log(`${playerName}: ${finalMood}`);
}
 
function shout(mood) {
  return mood.toUpperCase();
}
 
printMood("Mina", "ready", shout);
printMood("Rae", "calm", mood => `feeling ${mood}`);

Mina: READY, then Rae: feeling calm.

printMood is the higher-order function. shout and mood => ... are callbacks because printMood receives them and calls them.

The key detail is the missing parentheses in printMood("Mina", "ready", shout). You pass shout, not shout(). Passing shout gives the function value to printMood; writing shout() would call it immediately and pass its return value instead.

Functions can return functions

Higher-order functions can also build new functions. The returned function remembers where it was born, which is the closure idea from 5.4 doing useful work:

function makeScoreLabeler(label) {
  return score => `${label}: ${score} points`;
}
 
const practiceLabel = makeScoreLabeler("Practice");
const finalLabel = makeScoreLabeler("Final");
 
console.log(practiceLabel(18));
console.log(finalLabel(42));

Practice: 18 points, then Final: 42 points.

makeScoreLabeler returns a function. Each returned function keeps its own label from its birthplace. You do not need to reopen the closure machinery here; the practical result is simple: a function can make a more specific function.

This pattern shows up anywhere you want to set one detail now and use it later.

Callbacks are a timing pattern

Callbacks are not just about shorter syntax. They let one function control when another function runs.

function finishRound(playerName, score, onFinish) {
  console.log("Round finished.");
  onFinish(playerName, score);
}
 
finishRound("Mina", 22, (winner, score) => {
  console.log(`${winner} won with ${score} points.`);
});

Round finished., then Mina won with 22 points.

finishRound owns the timing. The callback owns the custom behavior. That split is the pattern underneath events, things that happen later like clicks or key presses, and asynchronous work, work that finishes later like timers or network requests. Parts III and IV make those real; for now, the shape is enough: "when the moment arrives, call this function."

Try it — hand-build map and filter

An array is an ordered bundle of values. Section 6 gives arrays their full treatment, but you already have enough tools to demystify two famous callback patterns.

Mapping means turning each item into one new item. Filtering means keeping only the items that pass a test.

javascript — live consolereal engine
⌘/Ctrl + Enter to run

[] starts an empty array. results[results.length] = ... places the next value at the end. That is just enough array syntax for today; Section 6 will slow down and name every moving part.

Notice the callback jobs:

  • transform answers, "what should this item become?"
  • shouldKeep answers, "does this item stay?"

The higher-order function owns the loop. The callback owns the one small decision. That split is why map and filter feel so good once arrays arrive: you stop rewriting the loop and swap in the one decision that changes.

At the prompt, try mapItems(["Mina", "Rae"], name => name.toUpperCase()) or filterItems([3, 12, 5], score => score >= 10).

The callback rule of thumb

Reach for a callback when a function knows the structure of the work but needs you to supply one custom step.

For example:

  • printMood knows how to print; the callback knows how to format.
  • finishRound knows when the round ends; the callback knows what to do next.
  • mapItems knows how to walk the ordered bundle; the callback knows how to transform one item.
  • filterItems knows how to walk the ordered bundle; the callback knows whether one item should stay.

That is the heart of higher-order functions: one function handles the boring structure, and another tiny function handles the interesting choice.

Checkpoint

Answer all three to mark this lesson complete

You have now seen why JavaScript treats functions as values: they let you separate the shape of work from the custom choice inside it. Next, Section 6 gives arrays the spotlight, so map, filter, and ordered data stop being a preview and become everyday tools.

+50 XP on completion