DEV Community

Cover image for An Intro To JS Higher Order Array Functions
Mike Cronin
Mike Cronin

Posted on • Updated on

An Intro To JS Higher Order Array Functions

One of the most crucial skills in JS is understanding how higher order and callback functions work. Simply put, a higher order function is a function that: 1) takes a different function as an argument and/or 2) returns a new function. That's it. A callback function is just the function that gets passed in. These are comp-sci words that hides simple concepts. For example, this is basically all forEach does:

const fakeForEach = (arr, callbackFunction) => {
  for (let i = 0; i < arr.length; i++) {
    const value = arr[i]
    const index = i;
    const givenArr = arr;
    callbackFunction(value, index, givenArr)
  }
}

const myArr = ['a', 'b', 'c']
const myCallback = (val, idx, arr) => {
  console.log('Value at index:', val);
  console.log('Current index:', idx);
  console.log('Original array:', arr);
};

// these will log the same things!
fakeForEach(myArr, myCallback);
myArr.forEach(myCallback);
Enter fullscreen mode Exit fullscreen mode

By passing in a function but not calling it we allow a higher order function, in this case fakeForEach or .forEach to invoke it with each iteration of the loop. Now lets break down some the major higher order array functions that come built in with JS.

Also, you can of course define the callback functions inline, but for the following examples, I'm explicitly creating a variable just so it's perfectly clear what the callback refers to.

// inline
arr.forEach((val) => {
  console.log(val)
});

// variable
const callback = (val) => {
  console.log(val)
});
arr.forEach(callback);

// both are fine!
Enter fullscreen mode Exit fullscreen mode

.forEach

Function Description

.forEach iterates through an array without caring about return values. If you essentially want a basic loop or mutate an existing object, this is your method.

Callback Description

The callback for forEach takes in the value, the index, and the original array during each iteration. The return value of the provided callback is ignored.

Example

const letters = ['a', 'b', 'c'];
const callback = (val, idx, arr) => {
  console.log('Value at index:', val);
  console.log('Current index:', idx);
  console.log('Original array:', arr);
};
letters.forEach(callback);
// Value at index: a
// Current index: 0
// Original array: [ 'a', 'b', 'c' ]
// Value at index: b
// Current index: 1
// Original array: [ 'a', 'b', 'c' ]
// Value at index: c
// Current index: 2
// Original array: [ 'a', 'b', 'c' ]
Enter fullscreen mode Exit fullscreen mode

.map

Function Description

.map is a lot like forEach, except that it builds up and returns a new array.

Callback Description

Like forEach, the provided callback gives you access to the value, the index, and the original array. Each individual return value from the callback is what gets saved to the new array.

Example

const numbers = [10, 20, 30];

const callback = (val, idx, arr) => {
  console.log('Value at index:', val);
  console.log('Current index:', idx);
  console.log('Original array:', arr);
  return val * 100;
};
const bigNumbers = numbers.map(callback);

console.log('bigNumbers: ', bigNumbers);
// bigNumbers:  [ 1000, 2000, 3000 ]
Enter fullscreen mode Exit fullscreen mode

.filter

Function Description

filter is used to return a new array based off of values that pass a condition.

Callback Description

The callback has the value, index, and array, however it is the return value that's interesting. If an iteration has a truthy return value, the value in the array at that iteration gets saved to the new array.

const names = ['tom', 'ezekiel', 'robert'];

const callback = (val, idx, arr) => {
  return val.length > 3;
};
const longNames = names.filter(callback);
console.log('longNames: ', longNames);
// longNames:  [ 'ezekiel', 'robert' ]
Enter fullscreen mode Exit fullscreen mode

.some

Function Description

Some returns a boolean if at least one of the elements in the array meets the given condition.

Callback Description

It's a standard value, index, arr situation. However, unlike the other methods so far, once the callback returns true, some will stop iterating through the array. That's becuase theres no need to keep going. Remember, some only cares if there's at least one value, if you want the exact number of truthy values, you should use either forEach and keep a count variable, or filter and then just use the length of the new array. The only way some will iterate through the entire array is if it never finds a value that returns a truthy value. At which point some will return false.

Example

const numbers = [1, 4, 9001, 7, 12];
const callback = num => {
  console.log('num: ', num);
  return num > 9000;
};
const isOver9000 = numbers.some(callback);
// num:  1
// num:  4
// num:  9001

console.log('isOver9000: ', isOver9000);
// isOver9000:  true
Enter fullscreen mode Exit fullscreen mode

.every

Function Description

every returns a boolean, true if every value in the array passes the callback's condition, and false otherwise.

Callback description

The callback has the value, index, and array, we've come to know and love. It works exactly like some, where it evaluates the return values as truthy/falsy. However, it abandons iteration if a single value returns falsy, which is the opposite of some. It's kind of like || vs && short circuiting.

Example

const numbers = [9001, 9002, 7, 12];

const callback = (num) => {
  console.log('num: ', num);
  return num > 9000;
}
const areAllOver9000 = numbers.every(callback)
// num:  9001
// num:  9002

console.log('areAllOver9000: ', areAllOver9000);
// areAllOver9000:  false
Enter fullscreen mode Exit fullscreen mode

The more complicated iterators

The next methods depart somewhat from the val, idx, arr pattern of callbacks and are a little more complicated. As such, let's explain them a little more in depth.


.reduce (basic use case)

This method reduces an array of values into a single one. The provided callback's first argument is the accumulator. The second argument is the current value. The main trick with reduce is that whatever the iterator returns from one iteration becomes the accumulator for the next. The final return value of reduce is whatever the accumulator has been built up to by the final iteration.

What about the first iteration?

reduce has an optional, but highly recommended, second argument that sets the initial value for the accumulator. If no initial value is provided, reduce will essentially take the first value of the array, treat that as the initial value and the second value in the array as the current value. In general, you should just always provide an initial value, as it leads to fewer bugs.

const numbers = [12,8,23,5];
const startingVal = 0;
const callbackFn = (accumulator, currentVal) => {
  console.log('Accumulator', accumulator);
  console.log('Value at index:', currentVal);
  // console.log('Current index:', idx);
  // console.log('Original array:', arr);
  return accumulator + currentVal;
}

const total = numbers.reduce(callbackFn, startingVal);
// Accumulator 0
// Value at index: 12
// Accumulator 12
// Value at index: 8
// Accumulator 20
// Value at index: 23
// Accumulator 43
// Value at index: 5
console.log('total', total);
// total: 48
Enter fullscreen mode Exit fullscreen mode

.reduce (advanced use case)

At the end of the day, reduce just adds things up into an accumulator. But no one said the accumulator couldn't be...an object?? Look how you can use reduce to build up an object. For comparison, we do the exact same thing but using .forEach. The crucial thing to remember is now the initial value must be explicitly set an object. Also, we don't need them in this case, but the idx and arr parameters are still available.

const arr = ['x', 'y', 'z', 'z', 'x', 'z'];
const countForEach = (arr) => {
  const result = {};
  arr.forEach((letter) => {
    result[letter] = (result[letter]) ? result[letter] + 1 : 1;
  });
  return result;
};

const countReduce = (arr) => arr.reduce((acc, letter) => {
  acc[letter] = acc[letter] ? acc[letter] + 1 : 1;
  return acc;
}, {});

console.log(countForEach(arr));
// { x: 2, y: 1, z: 3 }
console.log(countReduce(arr));
// { x: 2, y: 1, z: 3 }
Enter fullscreen mode Exit fullscreen mode

.sort

The default sort() method sorts things alphabetically. Which means [1, 3, 2, 11] would get sorted into [1, 11, 2, 3].` This is not ideal. To sort numbers correctly, you need to pass in a compare callback function. The compare function needs to return a positive number, a negative number, or 0. JS will then use these numbers to determine if the values are in the right order. So kind of like this:

js
const compare = (a, b) => {
if (a is less than b by some ordering criterion) {
return a negative number;
}
if (a is greater than b by the ordering criterion) {
return a positive number;
}
// a must be equal to b
return 0;
}

That's a very manual setup, which may be useful for sorting non-numeric values. However, if you're comparing numeric values, you can get away with a drastically simpler callback that still does the same thing:

js
// sorts smallest to biggest (ascending)
let compare = (a, b) => a - b;
// sorts biggest to smallest (descending)
compare = (a, b) => b - a;

Used in context, sort looks like so.

js
const numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log('numbers:', numbers);
// [ 1, 2, 3, 4, 5 ]

The compare function can easily deal with objects as well, simply access whatever property to need.

js
const houses = [
{color: 'blue', price: 350000},
{color: 'red', price: 470000},
{color: 'pink', price: 280000},
];
houses.sort((a,b) => a.price - b.price)
console.log('houses:', houses);
// houses [
// { color: 'pink', price: 280000 },
// { color: 'blue', price: 350000 },
// { color: 'red', price: 470000 }
// ]

Something important to note here is that unlike the other iterator functions on this list, sort is not pure; it will mutate the original array instead of making a new one.

More higher order functions await!

This is just the base of the higher order mountain, there is so much more to explore about this concept. But, you should now have a pretty good grasp on the basics, and I encourage you to open up a console and play around with the values until it just feels second nature.

happy coding everyone,

mike

Top comments (0)