Detailed explanation
A higher-order function is a function that accepts another function as an argument, returns a function, or does both. JavaScript supports this because functions are first-class values: you can assign them to variables, store them in objects, pass them into calls, and return them like other values. Higher-order functions let you separate reusable control flow from the specific operation you want to perform. They are not necessarily asynchronous; accepting or returning a function is what defines them.
In the example, withLog is a higher-order function because it receives fn and returns an arrow function. Calling withLog((a, b) => a + b) creates the wrapper but does not execute the addition. The variable add refers to that wrapper. When add(2, 3) runs, the rest parameter collects the arguments into an array, console.log prints that array, and the spread syntax passes those arguments to fn. Returning fn(...args) preserves the original function's result, so the call returns 5. The wrapper retains access to fn through a closure, even after withLog has finished executing.
Common examples include map, filter, and reduce, which accept callbacks describing how to transform, select, or combine array elements. A debounce utility usually accepts a function and returns a wrapper that delays execution until calls stop for a specified interval. Middleware factories and composition utilities can also be higher-order functions, although the label middleware alone does not guarantee this. These patterns make logging, timing, validation, and other reusable behaviour easier to add without copying logic into every function.
Interviewers usually check whether you can identify the higher-order function, distinguish it from its callback, and explain when each function executes. They may also ask about closures and wrapper correctness. This particular arrow-function wrapper does not forward a caller's dynamic this value to fn, so it can break methods that depend on their receiver. A general-purpose wrapper can instead return a regular function and call fn.apply(this, args). Higher-order functions are therefore useful abstractions, but their return values, arguments, and execution context still require careful handling.
Key points to remember
- A higher-order function accepts a function, returns a function, or both.
- First-class functions make higher-order functions possible in JavaScript.
- In withLog, fn is the supplied function and add is the returned wrapper.
- Closures let returned wrappers retain access to functions and other captured state.
- Wrappers should preserve the required arguments, return value, and this context.
Common mistakes candidates make
- Assuming every callback is itself a higher-order function; it must also accept or return a function to qualify.
- Claiming higher-order functions are inherently asynchronous, even though map and filter invoke callbacks synchronously.
- Forgetting to return the wrapped function's result or assuming an arrow wrapper forwards dynamic this.
Likely follow-up questions
How is a higher-order function different from a closure?
Higher-order describes a function that accepts or returns functions. A closure is a function together with access to its surrounding lexical environment; the returned wrapper in withLog closes over fn.
Why pass a function instead of calling it immediately?
Passing a function lets the receiving function decide when and how to invoke it. Passing fn() instead executes fn immediately and supplies its result, which may not be a function.
How would you preserve this in withLog?
Return a regular function: function (...args) { console.log(args); return fn.apply(this, args); }. This forwards the wrapper's call-time receiver, but an extracted method still needs an appropriate receiver or explicit binding.
Interview tip: State the definition, trace withLog through add(2, 3), and finish with one practical use and the this-context caveat.
