In our journey through TypeScript generics, we’ve covered the basics, interfaces, and classes. Now, it’s time to explore advanced concepts by combining generics with higher-order functions. These functions, which take other functions as arguments or return them, unlock a new level of flexibility and complexity.
Understanding Higher-Order Functions
Higher-order functions are functions that operate on other functions, either by taking functions as arguments or returning them as results. They are a fundamental concept in functional programming and can lead to cleaner, more modular, and more versatile code.
In TypeScript, higher-order functions can be made even more powerful when combined with generics. Let’s see how this works.
Creating a Generic Map Function
One common higher-order function is map, which applies a given function to each element of an array and returns a new array with the results. With generics, we can create a generic map function that works with various data types.
function customMap<T, U>(array: T[], mapper: (item: T) => U): U[] {
const result: U[] = [];
for (const item of array) {
result.push(mapper(item));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
const doubled = customMap(numbers, (num) => num * 2); // Returns [2, 4, 6, 8, 10]
const words = ["apple", "banana", "cherry"];
const lengths = customMap(words, (word) => word.length); // Returns [5, 6, 6]function customMap<T, U>(array: T[], mapper: (item: T) => U): U[] {: This is a higher-order function namedcustomMap. It takes two type parameters,TandU, representing the input and output data types. It also takes two parameters:array, which is an array of typeT, andmapper, a callback function that takes an item of typeTand returns an item of typeU.const result: U[] = [];: Inside the function, an empty arrayresultof typeUis created to store the mapped values.for (const item of array) { result.push(mapper(item)); }: This loop iterates over each item in thearray. For each item, themapperfunction is called to transform it, and the result is pushed into theresultarray.return result;: The function returns theresultarray, which now contains the mapped values of typeU.
Usage of customMap:
const numbers = [1, 2, 3, 4, 5];: An arraynumberscontaining numbers is defined.const doubled = customMap(numbers, (num) => num * 2);: ThecustomMapfunction is called withnumbersas the input array and a callback function(num) => num * 2. This callback function doubles each number in the array. The result is assigned to thedoubledvariable, which will contain[2, 4, 6, 8, 10].const words = ["apple", "banana", "cherry"];: An arraywordscontaining strings is defined.const lengths = customMap(words, (word) => word.length);: ThecustomMapfunction is called withwordsas the input array and a callback function(word) => word.length. This callback function returns the length of each word in the array. The result is assigned to thelengthsvariable, which will contain[5, 6, 6].
In this example, the customMap function is a higher-order function that uses generics to allow mapping of arrays with different data types. It provides flexibility and type safety, and it’s used to perform mapping operations on arrays of numbers and strings.
Applying Generics to Callback Functions
Higher-order functions often take callback functions as arguments. With generics, we can make sure that the callback function’s input and output types align with the higher-order function’s expectations.
function findFirst<T>(array: T[], predicate: (item: T) => boolean): T | undefined {
for (const item of array) {
if (predicate(item)) {
return item;
}
}
return undefined;
}
const numbers = [1, 3, 5, 7, 9];
const isEven = (num: number) => num % 2 === 0;
const firstEven = findFirst(numbers, isEven); // Returns 1In this example, findFirst is a generic higher-order function that finds the first element in an array that satisfies a given predicate. The predicate callback function ensures that the input matches the array’s data type and the output is a boolean.


