skip to content

Higher-Order Functions and Callbacks

A higher-order function takes a function as an argument, returns one, or both — the pattern behind map, event handlers, middleware, and every function factory. Interviewers ask you to write one from scratch to check that you genuinely see functions as values rather than syntax.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

6

In JavaScript, what makes a function a higher-order function, and which property of the language makes higher-order functions possible?

level: juniorimportance: must knowfreq 82%

answer

  1. functions are values, not syntax
  2. two shapes: takes one, returns one
  3. callback is a role, not a type
  4. name versus name-with-parentheses

basics

~20 s

A higher-order function takes a function as an argument, returns a function, or both. It works because JavaScript functions are first-class values: they can be stored in variables, put in arrays or objects, passed as arguments, and returned like any other value.

solid answer

~40 s

A function is higher-order if it operates on functions — it accepts one or more functions as arguments, returns a function, or does both. Nothing special enables this: in JavaScript a function *is* a value, a callable object, so `const f = function () {}` stores it, `arr.push(f)` puts it in an array, and `run(f)` hands it to someone else exactly like a number or a string. Array methods such as `map`, `filter` and `sort` are higher-order because they take a callback; a factory like `makeAdder(5)` is higher-order because it returns a function. The single mistake to avoid is writing `run(f())` when you meant `run(f)` — the parentheses call the function and pass its *result* instead of the function itself.

code

javascript · 14 lines
javascript
// Shape 1: takes a function
function applyTwice(fn, value) {
  return fn(fn(value));
}

// Shape 2: returns a function
function makeAdder(step) {
  return (n) => n + step;
}

const addTen = makeAdder(10);
console.log(applyTwice(addTen, 5)); // 25
console.log(typeof addTen);         // "function"
console.log(addTen.name);           // "" (anonymous arrow returned from makeAdder)

go deeper

for a junior

Be ready to state the definition and write both shapes on a whiteboard: one function that takes another, one that returns another. Say plainly that functions are values in JavaScript.

for a middle

Explain the argument contract the higher-order function imposes — how many arguments the callback receives and what its return value is used for — and show the passing-versus-calling bug and how you spot it in review.

for a senior

Show judgment about when the abstraction pays for itself. An interviewer expects you to point out that a callback inverts control: the caller supplies behaviour but the callee owns timing, error handling, and how many times the callback runs.

for a principal

Own the API-design angle: whether an extension point should be a function, a configuration object, or data, and what handing users a callback commits your library to — invocation timing, argument stability, and error propagation you can no longer change without a breaking release.

## Functions are values In JavaScript a function is an ordinary value — specifically, an object that happens to be callable. Anything you can do with a number you can do with a function: bind it to a variable, store it in an array, use it as an object property, pass it to another function, or return it from one. This is what "first-class functions" means, and it is the entire foundation of the higher-order style. ```js const double = (n) => n * 2; const ops = [double, (n) => n + 1]; // stored in an array const registry = { double }; // stored as a property console.log(typeof double); // "function" ``` Note that `typeof` reports `"function"`, but a function is still an object underneath: it has properties (`double.name` is `"double"`), and you can attach your own. ## The two shapes A higher-order function is any function that **takes a function as an argument**, **returns a function**, or both. Taking a function — the callback shape: ```js function applyTwice(fn, value) { return fn(fn(value)); } applyTwice((n) => n + 3, 1); // 7 ``` `applyTwice` knows nothing about what `fn` does. The caller supplies the behaviour; `applyTwice` supplies the control flow. That inversion is the whole point. Returning a function — the factory shape: ```js function makeAdder(step) { return (n) => n + step; } const addTen = makeAdder(10); addTen(5); // 15 ``` `makeAdder` is called once and produces a specialised function you can call many times. Standard-library examples of the first shape are everywhere: `Array.prototype.map`, `Array.prototype.filter`, `Array.prototype.sort` and `Array.prototype.forEach` all accept a callback; `Object.defineProperty` accepts getter and setter functions. ## Callback is a role, not a type The word *callback* describes a function's role rather than anything about the function itself: it is simply a function that somebody else will call, at a time of their choosing. Any function can be one. The receiving code decides how many arguments to pass it, when to call it, and what to do with its return value — that contract is defined entirely by the higher-order function, and it is why reading the documentation of the callback signature matters. ## Passing versus calling The most common beginner bug is a stray pair of parentheses: ```js function schedule(task) { /* stores task, runs it later */ } schedule(cleanup); // passes the function — usually what you want schedule(cleanup()); // CALLS cleanup now and passes its return value ``` Both lines are valid JavaScript, so nothing throws. The second one runs `cleanup` immediately and then hands `schedule` whatever it returned — very often `undefined`, which then fails much later with a confusing "task is not a function" message. Whenever you write a function name into an argument list, check whether you meant the name or the call. ## Why the pattern is worth learning Higher-order functions let you factor out *what varies*. Instead of writing one loop per operation, you write one traversal that accepts the operation. Instead of ten near-identical validators, you write one validator factory parameterised by a rule. Event-driven and asynchronous APIs are unavoidably higher-order — you cannot hand the runtime code to run later except by handing it a function. Composition helpers, wrappers such as `once` or `memoize`, and middleware chains are all just functions that take and return functions. A small warning about vocabulary: not every function-taking function is worth abstracting. Interviewers ask you to write a higher-order function from scratch mainly to check that you really see a function as a value, not as a piece of syntax that must be immediately invoked. Demonstrating that by writing a four-line factory is worth more than reciting a definition.

  • Is every function that accepts a callback worth calling higher-order, or is the term reserved for library code?
    The term is purely structural — any function whose parameter or return value is a function qualifies, whether it is a three-line helper in your own file or `Array.prototype.map`. There is no threshold of sophistication. What varies is usefulness: the abstraction earns its keep when it removes real duplication, not merely because it accepts a function.
  • What actually happens if I write `schedule(cleanup())` instead of `schedule(cleanup)`?
    `cleanup` runs immediately, at the moment the argument list is evaluated, and its return value is passed to `schedule`. If `cleanup` returns nothing, `schedule` receives `undefined`, stores it, and blows up later with something like "task is not a function" — far from the real mistake. Nothing throws at the call site, which is what makes it a slow bug.
  • How can a function be both an object and callable at the same time?
    Because a JavaScript function is an object with an internal call behaviour. `typeof` reports `"function"` to flag that it is callable, but it still has properties: `name`, `length`, and any property you attach yourself. That is why you can write `handler.cancelled = true` on a function you passed somewhere, and why functions can live in arrays and Maps like any other object.

A recipe card is not the meal. Passing the card lets someone cook whenever they like; cooking it yourself and handing over the plate is a different act — that is the difference between passing fn and passing fn().

saying these in an interview costs you the question

  • Claims JavaScript needs special syntax to pass a function
  • Writes fn() in an argument list when passing fn was meant
  • Thinks only built-in array methods can be higher-order
  • Says a callback must be anonymous or an arrow function
  • Confuses returning a function with returning its call result

context

open as a page

What does `['1', '7', '11'].map(parseInt)` return, and what does that result reveal about how a higher-order function calls the callback you hand it?

level: middleimportance: must knowfreq 60%

basics

~20 s

It returns [1, NaN, 3]. The callback is invoked with three arguments — element, index and array — so parseInt receives the index as its radix: radix 1 is invalid and radix 2 reads "11" as binary 3. Wrap the callback to control its arity.

open as a page

In JavaScript, write `compose` and `pipe` helpers that combine several single-argument functions into one. How do they differ, and what constrains the functions you can put in them?

level: middleimportance: should knowfreq 44%

basics

~20 s

Both fold a list of functions into one. pipe applies left to right, compose right to left, matching mathematical notation. Each stage receives exactly one value — the previous stage's return value — so every function after the first must take one argument and return the input the next one expects.

open as a page

Implement `once(fn)`: a higher-order function returning a wrapper that runs `fn` at most one time and returns that first result on every later call. What must the wrapper get right?

level: middleimportance: should knowfreq 45%

basics

~20 s

Return a wrapper holding a called flag and a cached result: on the first call it forwards every argument to fn, stores the return value and flips the flag; afterwards it skips fn and returns the cached value. Forwarding arguments and returning the value are the parts people drop.

open as a page

You are asked to write a general-purpose `memoize(fn)` wrapper for a production codebase. How do you build the cache key, and what makes memoization unsafe for a given function?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Memoize only pure, deterministic functions. Key a single primitive argument directly in a Map; for several arguments or object arguments you need an explicit key function, because stringifying arguments both collides and misses. Bound the cache — an unbounded one grows for the life of the process.

open as a page

You are designing a library function that accepts a user-supplied callback. What contract must you fix and document before shipping it, and why is inconsistent invocation timing the dangerous one?

level: principalimportance: should knowfreq 26%

basics

~20 s

Fix four things: which arguments the callback receives, how many times and in what order it may be called, whether it is invoked synchronously or always deferred, and what happens if it throws. Timing is the dangerous one because a function that sometimes calls back synchronously makes caller state nondeterministic.

open as a page