Dataslope logoDataslope

Higher-Order Functions

Functions that take functions, return functions, or both — the universal mechanism for abstraction in FP.

A higher-order function is a function that does at least one of two things:

  1. Takes a function as one of its arguments.
  2. Returns a function as its result.

That's the entire definition. The reason it's such a central concept is that passing behavior as a value is the most powerful form of abstraction a language can offer. Once you have it, you can replace whole categories of boilerplate with one-line calls.

Functions as values: the prerequisite

For higher-order functions to make sense, the language has to treat functions as first-class values — things you can store in variables, pass to other functions, return, and put in data structures. TypeScript inherited this from JavaScript, which inherited it from Scheme.

Code Block
TypeScript 5.7

The fact that (x: number) => number is a type you can use anywhere — as an array element, a record value, a parameter — is what makes everything else in this chapter possible.

Functions that take functions: the classic combinators

The three you already know:

Code Block
TypeScript 5.7

The key insight: map, filter, and reduce are one implementation each. They work for any element type, any transformation, any predicate, any accumulator. The behavior comes from the function you pass in. The combinator provides the plumbing.

Look at how much code you aren't writing:

  • map replaces "make a new array, loop, push transformed elements"
  • filter replaces "make a new array, loop, push elements that pass"
  • reduce replaces "init an accumulator, loop, update accumulator"

This is the abstraction higher-order functions provide: the control flow is generic; the behavior is the parameter.

Building your own higher-order function

Whenever you find yourself writing the same plumbing twice with a different inner action, factor out a higher-order function.

Code Block
TypeScript 5.7

The two functions share the same "wrap with timing" logic. The generic version is a single higher-order function:

Code Block
TypeScript 5.7

timed is a higher-order function in both directions: it takes a function (work) and runs it. We could equally well write a "timing decorator" that returns a wrapped function.

Functions that return functions: factories

Code Block
TypeScript 5.7

These returned functions are closures — they capture variables from the enclosing scope (amount, n, re, message). Closures are the technical mechanism that makes "function-returning-function" useful.

Decorating: wrapping behavior

Returning a function that wraps another function is called a decorator (the FP sense, unrelated to TypeScript's @decorator syntax). It lets you add cross-cutting concerns — logging, timing, retries, memoization, throttling — without modifying the inner function.

Code Block
TypeScript 5.7

The original add is unchanged and still usable. loggedAdd is a new function with the same signature, plus the wrapper behavior. That's the power of returning a function: you can layer new behaviors on top of existing ones without rewriting them.

A library of useful higher-order functions

These come up so often they're worth memorizing.

Code Block
TypeScript 5.7

Most "FP libraries" are, at their core, a curated collection of small higher-order functions like these. None of them are mysterious. All of them are six-line implementations.

Higher-order types: callbacks in signatures

When you write the type of a higher-order function in TypeScript, the function-as-value pattern shows up directly in the signature:

Code Block
TypeScript 5.7

The type (a: A) => A describes what the function expects. The compiler will reject any value that doesn't fit. That's how you get both flexibility and safety: behavior is a parameter, but it's a typed parameter.

A multi-file challenge

Challenge
TypeScript 5.7
Build a tiny validator library

In validators.ts, implement three higher-order helpers:

  • every(...vs) — combine validators; returns the first error or null.
  • when(pred, v) — run validator v only when pred(value) is true.
  • mapInput(f, v) — adapt a validator from one type to another (e.g. validate s.length with a number validator).

Then validate the sample data in main.ts.

Expected output

error: too short
error: missing @
ok

QuestionSelect one

Which of the following best describes a higher-order function?

A function that runs faster than other functions

A function defined at the top level of a module

A function that takes another function as an argument, returns a function, or both

A function annotated with TypeScript generics

On this page