Higher-Order Functions
MedA higher-order function (HOF) is a function that takes a function as an argument, returns a function, or both. HOFs are the backbone of functional programming in JavaScript — they enable abstraction, code reuse, and declarative patterns. Beyond array methods, HOFs power middleware, decorators, function factories, and compose/pipe utilities.
Interactive Visualization
for...in vs for...of
Step 1: Setup
const obj = { a: 1, b: 2 };
const arr = [10, 20, 30];Output
No output yet
1 / 5
Key Points
- A HOF takes a function as an argument or returns a function (or both)
- Function factories create specialized functions from general ones using closures
- The middleware pattern chains HOFs where each can modify input, output, or control flow
- Decorators wrap a function to add behavior without modifying the original
- compose(f, g)(x) applies right-to-left: f(g(x)); pipe applies left-to-right
- HOFs enable separation of "what to do" from "how to iterate"
Code Examples
Function Factory
function createMultiplier(factor) { return function (number) { return number * factor } } const double = createMultiplier(2) const triple = createMultiplier(3) console.log(double(5)) // 10 console.log(triple(5)) // 15 const prices = [9.99, 24.50, 3.75] console.log(prices.map(double)) // [19.98, 49, 7.5]
createMultiplier returns a new function. Each returned function closes over its own factor value.
Decorator Pattern
function withLogging(fn) { return function (...args) { console.log('Calling ' + fn.name + ' with', args) const result = fn(...args) console.log('Result:', result) return result } } function add(a, b) { return a + b } const loggedAdd = withLogging(add) loggedAdd(2, 3) // logs call + result, returns 5
Decorators wrap a function to add cross-cutting concerns. The original function is unchanged.
Compose and Pipe
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x) const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x) const trim = (s) => s.trim() const toLower = (s) => s.toLowerCase() const split = (s) => s.split(' ') const unique = (arr) => [...new Set(arr)] const process = pipe(trim, toLower, split, unique) console.log(process(' Banana Apple banana ')) // ['banana', 'apple']
compose reads right-to-left, pipe reads left-to-right. Both combine small functions into a pipeline.
Custom Retry HOF
function withRetry(fn, maxAttempts, delayMs) { return async function (...args) { let lastError for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(...args) } catch (err) { lastError = err if (attempt < maxAttempts) { await new Promise(r => setTimeout(r, delayMs * attempt)) } } } throw lastError } } const resilientFetch = withRetry(fetchUser, 3, 1000)
withRetry adds retry logic to any async function without modifying the original.
Common Mistakes
- Creating overly abstract HOFs that are harder to understand than the code they replace
- Forgetting to preserve function name and length when wrapping
- Not handling both sync and async return values in decorators
- Confusing a HOF with a method that happens to accept a callback
Interview Tips
- Give examples beyond map/filter: function factories, decorators, middleware
- Implement compose or pipe from scratch — interviewers love seeing reduce used elegantly
- Show a practical decorator like withRetry or withCaching
- HOFs enable the open-closed principle: extend behavior without modifying existing code