Higher Order Functions

Introduction: From Closures to Collections

In our previous lesson, we explored how closures allow functions to remember the environment where they were created. We used this "memory" to create private variables and factory functions capable of generating specialized tools. This was our first step into functional programming, focusing on how we control individual pieces of data.

Now, we are going to expand that focus. In this lesson, we will learn how to apply those same functional principles to groups of data, such as lists of bank transactions. We will use higher-order functions, which are functions that either take other functions as arguments or return new functions as their results. By using these patterns, you can process large amounts of data in a clean, readable way that is much less likely to contain bugs.

The Core Trio: Filter, Map, and Reduce

To work with collections effectively, we rely on three primary methods built into JavaScript arrays. The first is filter, which acts like a sieve. It iterates through your array and retains only the items that pass a specific test. You provide a small function that returns true or false, and filter creates a new array containing only the items for which your function returned true.

The second method is map. While filter changes the number of items in a list, map changes the items themselves. It takes a function and applies it to every element in your array, creating a new array with the transformed results. It is the perfect tool for when you have a list of objects but only need a single property from each one, such as an amount or a name.

The third method is reduce. This is the most versatile of the three because it allows you to take an entire collection and "reduce" it to a single value, such as a sum, a string, or even a completely different object. It uses an accumulator to keep track of the result as it moves through each item in the list.

Building Pipelines with Method Chaining

One of the most powerful aspects of these functions is that they can be connected to form a pipeline. Because filter and map both return new arrays, you can call one right after the other. This creates a clear flow where data enters at the top and is transformed step-by-step until it reaches the final result.

Let’s look at how we can find the total sum of all deposits in a list of transactions.

"use strict";

const transactions = [
  { id: 1, type: "deposit",  amount: 200 },
  { id: 2, type: "withdraw", amount: 50  },
  { id: 3, type: "deposit",  amount: 300 },
  { id: 4, type: "withdraw", amount: 75  },
];

const totalDeposits = transactions
  .filter((t) => t.type === "deposit")
  .map((t) => t.amount)
  .reduce((sum, n) => sum + n, 0);

console.log("Total deposits:", totalDeposits);

In this pipeline, the code first uses filter to examine every transaction and keep only those where the type is "deposit". Once it has that smaller list, it uses map to discard the ID and type information, keeping only the numerical amount. Finally, reduce starts with a sum of 0 and adds every amount together.

Total deposits: 500

This approach is much cleaner than using a traditional loop because each step has a single, clear responsibility. It makes the code easier for other developers to read because they can see exactly how the data is being shaped at every stage.

Functions That Return Functions: Reusable Comparators

Earlier, we mentioned that higher-order functions can also return other functions. This is a direct application of the closures we learned about in the last lesson. We can write a factory function that creates "sorting" functions for us, allowing us to avoid writing the same comparison logic repeatedly.

In JavaScript, the sort method takes a function that compares two items. We can create a helper called by that simplifies this process.

const by = (key) => (a, b) => a[key] - b[key];

const sorted = [...transactions].sort(by("amount"));
console.log("Sorted by amount:", sorted.map((t) => t.amount));

The by function is a higher-order function. It takes a key (like "amount") and returns a new function that knows how to compare two objects using that specific key. When we call by("amount"), it creates a closure that remembers the word "amount" and uses it whenever the sorting logic runs.

Sorted by amount: [50, 75, 200, 300]

We used the syntax [...transactions] before sorting to create a shallow copy of the array. This is a common practice in functional programming because the standard sort method in JavaScript mutates the original array. By making a copy, we ensure our original data remains safe and unchanged.

Advanced Reduce: Grouping Data into Objects

While reduce is often used for math, it is also excellent for restructuring data. A common task in web development is taking a flat list and grouping it into categories. We can use reduce to build an object where each key is a category and each value is an array of items belonging to that category.

const grouped = transactions.reduce((acc, t) => {
  (acc[t.type] ??= []).push(t);
  return acc;
}, {});

console.log("Grouped:", grouped);

In this example, our reduce starts with an empty object {} as the initial value, which we call acc (the accumulator). For every transaction t, we look at its type. We use the nullish coalescing assignment operator ??= to check whether that category already exists in our object. If it doesn't, we initialize it as an empty array []. Then, we simply push the current transaction into that array.

Grouped: {
  deposit: [
    { id: 1, type: 'deposit', amount: 200 },
    { id: 3, type: 'deposit', amount: 300 }
  ],
  withdraw: [
    { id: 2, type: 'withdraw', amount: 50 },
    { id: 4, type: 'withdraw', amount: 75 }
  ]
}

This pattern is highly efficient because it processes the entire list in a single pass. It shows how reduce can transform a simple list into a complex, organized data structure.

Summary and Practice Preparation

In this lesson, we moved from managing private state with closures to processing collections of data using higher-order functions. We learned how to use filter, map, and reduce to create clean data pipelines. We also revisited the idea of factory functions to create reusable sorting logic and saw how reduce can be used to group data into organized objects.

These patterns are the bread and butter of modern JavaScript development. They allow you to write code that is expressive and easy to maintain. On the CodeSignal platform, all the tools you need are already set up in the IDE. In the upcoming practice exercises, you will apply these patterns to solve real-world data-processing challenges. Focus on how the data flows from one function to the next, and remember that each small function should do exactly one thing well. Good luck!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal