Higher-Order Functions

⭐ Interview Importance: HIGH
⏱️ Revision Time: 4 min

TL;DR

In JavaScript, functions are First-Class Citizens. This means functions are just regular objects. They can be assigned to variables, stored in arrays, passed as arguments, and returned from other functions.

A Higher-Order Function (HOF) is a function that does at least one of two things:

  1. Accepts another function as an argument (a Callback).
  2. Returns a new function.

Mental Model

How It Works

HOFs allow developers to abstract over actions, not just values.
Instead of writing a for loop that manually iterates over an array and does math, you pass a math function to Array.map(). map handles the iteration (the “how”), and your callback function handles the math (the “what”). This is the foundation of declarative programming.

Example

// 1. HOF that takes a function as an argument
function executeTwice(callback) {
  callback();
  callback();
}

executeTwice(() => console.log("Hello!")); // Prints Hello! twice.

// 2. HOF that returns a function
function createMultiplier(multiplier) {
  return function(value) {
    return value * multiplier;
  };
}

const double = createMultiplier(2);
console.log(double(10)); // 20

Common Output Question

const numbers = [1, 2, 3];
const result = numbers.map(parseInt);
console.log(result);

Q: What is the output and why?
A: [1, NaN, NaN].
This is a classic trap. Array.map passes three arguments to its callback: (value, index, array).
parseInt expects two arguments: (string, radix).
So, map calls parseInt like this:

  1. parseInt(1, 0) -> radix 0 defaults to base 10 -> 1.
  2. parseInt(2, 1) -> radix 1 is invalid in math -> NaN.
  3. parseInt(3, 2) -> binary only allows 0 and 1. ‘3’ is invalid -> NaN.
    (Fix: numbers.map(num => parseInt(num))).

Senior Interview Question

Q: Write a Higher-Order Function once(fn) that ensures a function can only be executed exactly one time, no matter how many times it is called.

A: This tests closures and HOFs together.

function once(fn) {
  let hasRun = false;
  let result;
  
  return function(...args) {
    if (!hasRun) {
      result = fn.apply(this, args);
      hasRun = true;
    }
    return result; // Return the cached result on subsequent calls
  };
}

const initialize = once(() => console.log("DB Connected!"));
initialize(); // "DB Connected!"
initialize(); // Does nothing, returns undefined