Article

Reduce over functions or Data, Data Everywhere

Treating functions as data lets you assemble logic the same way you combine values. Code is just a series of steps, so you can put those steps in an array and manipulate them with tools like reduce. To warm up, here is a clunky way to add numbers one at a time:

function addNumbersSequentially(n1, n2, n3, n4, n5, n6, n7) {
    const step1 = n1 + n2;
    const step2 = step1 + n3;
    const step3 = step2 + n4;
    const step4 = step3 + n5;
    const step5 = step4 + n6;
    const step6 = step5 + n7;

    return step6;
}

// Example usage:
const sumOutput = addNumbersSequentially(1, 2, 3, 4, 5, 6, 7);

Grouping the numbers into an array is cleaner and lets the program decide how many times to run:

function addNumbersSequentially(numbersArray) {
    let result = 0;
    for (let ii = 0; ii < numbersArray.length; ii++) {
        result += numbersArray[ii];
    }

    return result;
}

Array.reduce already does this work for us, so we can rely on it instead of reimplementing the pattern:

function reduceEquivalent(array, accumulator, initialValue) {
    let accumulatedValue = accumulator(initialValue);

    for (let ii = 0; ii < array.length; ii++) {
        accumulatedValue = accumulator(accumulatedValue, array[ii]);
    }

    return accumulatedValue;
}

function addNumbersAccumulator(currentTotal, number) {
    return currentTotal + number;
}

const result = reduceEquivalent([1, 2, 3, 4, 5, 6, 7], addNumbersAccumulator, 0);
console.log(result); // 28

And using the built-in reduce is even shorter:

function addNumbersSequentially(numbersArray) {
    return numbersArray.reduce(addNumbersAccumulator, 0);
}

The same idea works for functions themselves. Here is a verbose, multi-step calculation:

function processNumberDetailed(inputNumber) {
    const doubled = inputNumber * 2;
    const squared = doubled ** 2;
    const subtracted = squared - 15;
    const divided = subtracted / 3;
    const absolute = Math.abs(divided);
    const rounded = Math.round(absolute);

    return `The processed result is ${rounded}`;
}

// Example usage:
const output = processNumberDetailed(4);
console.log(output); // "The processed result is 9"

Breaking each step into its own function makes the intent clearer:

function doubleNumber(num) {
    return num * 2;
}

function squareNumber(num) {
    return num ** 2;
}

function subtractFifteen(num) {
    return num - 15;
}

function divideByThree(num) {
    return num / 3;
}

function convertToAbsolute(num) {
    return Math.abs(num);
}

function roundToNearestWhole(num) {
    return Math.round(num);
}

function formatResult(num) {
    return `The processed result is ${num}`;
}

With those helpers in place, we can treat the functions themselves as data and reduce over them:

const processingSteps = [
    doubleNumber,
    squareNumber,
    subtractFifteen,
    divideByThree,
    convertToAbsolute,
    roundToNearestWhole,
    formatResult,
];

function processNumberWithReduce(inputNumber) {
    return processingSteps.reduce((current, step) => step(current), inputNumber);
}

console.log(processNumberWithReduce(4)); // "The processed result is 9"

reduce keeps the accumulated value for you—whether that value is a running total or the output of the previous function. Treating functions as data unlocks simple, composable pipelines without the clutter of intermediate variables.