Higher-Order Functions & Functional Thinking
In JavaScript, functions are values you can pass around and return. That one idea powers callbacks, map/filter/reduce, and the clean "functional" style that React leans on heavily.
What you will learn
- Explain callbacks and higher-order functions in plain words
- Write a function that returns another function (and use currying)
- Apply pure functions and immutability for safer code
Functions are values
The big idea behind this whole lesson: in JavaScript a function is just a value, like a number or a string. That means you can store a function in a variable, pass it into another function, and even return a function from a function. Once you accept that, callbacks and the array methods you already use suddenly make sense.
A callback is simply a function you hand to another function so it can call it later. A higher-order function is the flip side: any function that takes a function as an argument, or returns a function. You have used these all along — map, filter and forEach are higher-order functions, and the little arrow you pass them is the callback.
<script>
// greet is a value we pass to repeat — that makes repeat a higher-order function
function repeat(action, times) {
for (let i = 1; i <= times; i++) {
action(i); // call the callback, giving it the count
}
}
repeat(function (n) {
document.write("Hello #" + n + "<br>");
}, 3);
</script>repeat does not know or care what the action is — it just calls whatever function you give it, three times, passing the current count. We hand it a small callback that prints a greeting. This separation ("repeat handles the looping; the callback handles what to do each time") is the heart of higher-order functions.
Note: Output: Hello #1 Hello #2 Hello #3
Watch out: Pass a function without parentheses: repeat(myFn, 3) hands over the function itself. Writing repeat(myFn(), 3) would call myFn immediately and pass its return value instead — a very common beginner mix-up.
Returning a function — and currying
Because functions are values, a function can build and return another function, tailored with some remembered setting. (This relies on closures, which you met earlier.) A function that returns another function so you can supply arguments one at a time is called currying.
<script>
// multiplyBy returns a NEW function that remembers its factor
function multiplyBy(factor) {
return function (n) {
return n * factor; // factor is remembered (closure)
};
}
const double = multiplyBy(2); // a ready-made doubling function
const triple = multiplyBy(3); // a ready-made tripling function
document.write(double(5) + ", " + triple(5));
</script>multiplyBy(2) does not multiply anything yet — it returns a brand-new function that remembers factor = 2. We store that as double. Calling double(5) runs the returned function with the remembered factor, giving 10. triple is the same factory called with 3. One small factory produces many specialised functions — a clean, reusable pattern.
Follow how the factory builds a ready-made function step by step:
multiplyBy(2)runs and returns the inner function — it does not multiply yet.- That returned function remembers
factor = 2in its closure, and we store it asdouble. multiplyBy(3)is the same factory called again, producing a separate function that remembersfactor = 3, stored astriple.- Now
double(5)runs the first returned function withn = 5→5 * 2= 10. triple(5)runs the second returned function withn = 5→5 * 3= 15.
Note: Output: 10, 15
Pure functions & immutability — the functional style
Functional programming favours pure functions. A pure function (1) always returns the same output for the same input, and (2) has no side effects — it does not change anything outside itself (no editing global variables, no changing the page, no surprises). Pure functions are easy to test, easy to reason about, and never cause spooky bugs.
A key habit is immutability: instead of changing existing data, you create a new copy with the change. Compare an impure version that mutates an array with a pure one that returns a fresh array.
<script>
const prices = [100, 200, 300];
// IMPURE: changes (mutates) the original array — a side effect
function addImpure(arr, value) { arr.push(value); return arr; }
// PURE: returns a NEW array, leaving the original untouched
function addPure(arr, value) { return [...arr, value]; }
const result = addPure(prices, 400);
document.write("New: " + result.join(", ") + "<br>");
document.write("Original: " + prices.join(", "));
</script>addImpure uses push, which mutates the array it was given — the caller’s original data silently changes (a side effect). addPure instead spreads the old items into a new array and adds the value, so it returns a fresh list and the original prices stays exactly as it was. The output proves the original is unchanged — that predictability is why the functional style is prized.
Note: Output: New: 100, 200, 300, 400 Original: 100, 200, 300 (The original array is untouched — addPure did not change it.)
Tip: This is exactly how React expects you to work: never mutate state directly — always produce a new value (often with spread or map). Mastering pure functions and immutability here makes React’s rules feel obvious instead of arbitrary.
Q. Which best describes a higher-order function?
✍️ Practice
- Write a higher-order function
applyTwice(fn, value)that returnsfn(fn(value)). - Write a curried
greeting(word)that returns a function sogreeting("Hi")("Asha")gives "Hi, Asha". - Rewrite a function that mutates an array into a pure version that returns a new array.
🏠 Homework
- Take a small array of numbers and, using only pure operations (
map,filter,reduce— no mutation), produce the sum of the squares of the even numbers.