On this page
Advanced Functions
Advanced function techniques enable reusable, composable, and performant code. This chapter builds on scope and closures from the intermediate level.
Higher-Order Functions
Functions that take other functions as arguments or return functions:
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, i => console.log(i)); // 0, 1, 2
function greaterThan(n) {
return x => x > n;
}
[1, 5, 10].filter(greaterThan(4)); // [5, 10]
Currying
Transform a function with multiple arguments into a chain of single-argument functions:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...more) => curried(...args, ...more);
};
}
const add = curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2)(3)); // 6
Partial Application
Fix some arguments of a function:
function partial(fn, ...fixedArgs) {
return (...remainingArgs) => fn(...fixedArgs, ...remainingArgs);
}
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
const sayHello = partial(greet, 'Hello');
console.log(sayHello('Alice')); // 'Hello, Alice!'
Debounce
Delay execution until after a pause in calls — ideal for search input:
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce((query) => {
console.log('Searching:', query);
}, 300);
// Only fires 300ms after user stops typing
search('j');
search('ja');
search('jav');
search('java');
Throttle
Limit execution to at most once per interval — ideal for scroll/resize:
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
window.addEventListener('scroll', throttle(() => {
console.log('Scroll handled');
}, 200));
Function Composition
Combine functions into a pipeline:
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
console.log(compose(square, double, addOne)(3)); // ((3+1)*2)^2 = 64
console.log(pipe(addOne, double, square)(3)); // same result
Memoization
Cache function results for repeated inputs:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const fib = memoize(function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
console.log(fib(40)); // fast due to caching
These patterns appear throughout production JavaScript in frameworks, utilities, and performance-critical code.