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.