Hopefully this reduces the confusion.
In my experience learning and teaching JavaScript, reduce
is one of the toughest concepts to crack. In this article I’ll try to address one core question…
What is
reduce
and why is it called that?
Reduce Has Many Names
Some of them, according to Wikipedia, are
- Reduce
- Fold
- Accumulate
- Aggregate
- Compress
They all hint at the core idea. It’s all about breaking a structure down into a single value.
Reduce - A function that folds a list into any data type.
It’s like folding a box! With reduce
you can turn an array [1,2,3,4,5]
into the number 15
by adding them all up.
The Old Fashioned Way
Normally you’d need a loop to “fold” a list into a number.
const add = (x, y) => x + y;
const numbers = [1, 2, 3, 4, 5];
let total = 0;
for (let i = 0; i < numbers.length; i++) {
total = add(total, numbers[i]);
}
console.log(total); // 15
The Cool Kids Way
But with reduce
you can plug in your add
function and the loop is handled for you!
const add = (x, y) => x + y;
const numbers = [1, 2, 3, 4, 5];
numbers.reduce(add);
// 15
You’re literally folding 1-5 to get 15.
The Big Three
Before diving deeper I think it’s important to analyze reduce
alongside its famous companions–map
and filter
. They heavily overshadow reduce
, making it look like the weirdo of the bunch.
Despite their respective popularities, combining these three titans allows you to manipulate lists however you want!
For a moment humor me and pretend JavaScript can’t use loops, recursion, or array methods like forEach
, some
, find
, etc. The only three left are map
, filter
, and reduce
.
Our job as programmers hasn’t changed, however. We still need three types of functionality in our applications.
- Transforming lists
- Filtering lists
- Turning lists into other data types (number, string, boolean, object, etc).
Let’s see how our only tools–map
, filter
, reduce
–handle this challenge.
✅ Array.map transforms lists
Turning lists into other lists is Front-End development in a nutshell. Therefore map
covers much of your list work.
Let’s say our application calls an API for the list of users, and we need every user’s name displayed on the screen. Simply create a function that returns one user’s name.
const getUserName = (user) => user.name;
And plug it into map
to run that against an entire list of users.
users.map(getUserName);
// ['Marie', 'Ken', 'Sara', 'Geoff', ...]
✅ Array.filter judges lists
What if you want a new list with some items removed, like when the user searches their contact list? Simply create a function that returns true
or false
based on its input (a predicate).
const isEven = (x) => x % 2 === 0;
And plug it into filter
to apply that against an entire list.
const numbers = [1, 2, 3, 4, 5];
numbers.filter(isEven);
// [2, 4]
✅ Array.reduce does all that, and more
When map
and filter
aren’t enough, you bring in the big guns. The reduce
method can do what map
/filter
do, and anything else that involves looping over an array.
For example how would you calculate the total age of your users? Our users’ ages are 25, 22, 29, and 30.
const users = [
{ name: 'Marie', age: 25 },
{ name: 'Ken', age: 22 },
{ name: 'Sara', age: 29 },
{ name: 'Geoff', age: 30 }
];
map
and filter
can only return arrays, but we need a number
!
users.map(?);
users.filter(?);
// Nope! I need a number, not arrays.
If we had loops we’d just go through users
and tally their ages in a counter! Well what if I told you it’s even easier with reduce
?
users.reduce((total, currentUser) => total + currentUser.age, 0);
// 106
Log it out
I think the easiest way to digest this is to console.log
at each step.
const users = [
{ name: 'Marie', age: 25 },
{ name: 'Ken', age: 22 },
{ name: 'Sara', age: 29 },
{ name: 'Geoff', age: 30 }
];
const reducer = (total, currentUser) => {
console.log('current total:', total);
console.log('currentUser:', currentUser);
// just for spacing
console.log('\n');
return total + currentUser.age;
};
users.reduce(reducer, 0);
Here’s a screenshot from Chrome DevTools.
Break It Down
As you just saw, Array.reduce
takes two parameters.
- The reducer
- An initial value (optional)
The reducer is the function doing all the work. As reduce
loops over your list, it feeds two parameters to your reducer.
- An accumulator
- The current value
The current value is self-explanatory, just like when you use array[i]
in a regular loop. The accumulator, though, is a scary-sounding computer science term that’s actually simple.
Accumulator is the eventual return value
When you’re looping through the users
, how are you keeping track of their total age? You need some counter variable to hold it. That’s the accumulator. It’s the eventual value reduce
will spit out when it’s done.
At every step in the loop, it feeds the last accumulator and the current item to your reducer. Whatever the reducer returns becomes the next accumulator. The cycle ends when the list is finished and you have a single reduced value.
Initial value is optional
The second parameter to reduce
is the initial value. If you don’t supply it, reduce
defaults to the list’s first element.
This is fine if you’re summing plain numbers.
[1, 2, 3].reduce((total, current) => total + current);
// 6
But breaks if you use an object or array because you shouldn’t be adding those things up.
[{ age: 1 }, { age: 2 }, { age: 3 }].reduce((total, obj) => total + obj.age);
// [object Object]23
// Broken result, use an initial value.
In this case you should give the initial value of 0
.
[{ age: 1 }, { age: 2 }, { age: 3 }].reduce((total, obj) => total + obj.age, 0);
// 6
// Initial value fixes it.
// 0 + 1 + 2 + 3 = 6
Let’s Recreate Reduce
What I cannot create, I do not understand - Richard Feynman
Hopefully I’ve helped you so far. Now it’s time to write your own reduce
function to really hammer this home.
It’ll be a function that takes three parameters.
- A reducer
- An initial value
- An array to operate on
For this demo the initial value is not optional.
const reduce = (reducer, initialValue, array) => {
let accumulator = initialValue;
for (let i = 0; i < array.length; i++) {
const currentItem = array[i];
accumulator = reducer(accumulator, currentItem);
}
return accumulator;
};
Amazing just 10 lines of code, 6 key steps. I’ll go one by one.
- Define
reduce
and its three parameters. - Initialize the
accumulator
using the providedinitialValue
. This variable will change every loop. - Start looping over the array.
- Capture the array’s
currentItem
for that cycle. - Call
reducer
with theaccumulator
andcurrentItem
, saving it as a newaccumulator
. - When the loop’s finished and the
accumulator
is done changing, return it.
Miscellaneous History
I wanted to talk more about the history of reduce
and reducers, but wasn’t quite sure where to fit it. Nonetheless it’s very interesting!
Reducers are ancient
Redux made reducers cool to JavaScript developers, but it didn’t invent them. It’s actually not clear who coined the term, but here’s a few references I dug up.
Recursion Theory (1952)
This book from 1952 discusses reduce
from a metamathematical perspective, referring to it as fold
.
Lisp Programmer’s Manual (1960)
The Lisp Programmer’s Manual from 1960 has a section on the reduce
function.
Introduction to Functional Programming (1988)
This book from 1988 talks about using reduce
to turn lists into other values.
Bottom line–it’s an old topic. The more you study computer science the more you realize we’re mostly re-wrapping concepts discovered decades ago.
The more you study computer science the more you realize we're mostly re-wrapping concepts discovered decades ago.
— Yazeed Bzadough (@yazeedBee) October 13, 2019
Exercises For You
For the sake of time, we end here. However I hope I’ve at least hinted that reduce
is incredibly powerful and useful way beyond just summing numbers.
If you’re interested try these exercises and message me about them later. I may write a follow up article on them.
- Reimplement the Array.map function using
reduce
. - Reimplement the Array.filter function using
reduce
. - Reimplement the Array.some function using
reduce
. - Reimplement the Array.every function using
reduce
. - Reimplement the Array.find function using
reduce
. - Reimplement the Array.forEach function using
reduce
. - Turn an array into an object using
reduce
. - Turn a 2D array into a 1D (flat) array using
reduce
.
Want Free Coaching?
If you’d like to schedule a free 15-30 minute call to discuss Front-End development questions regarding code, interviews, career, or anything else follow me on Twitter and DM me.
After that if you enjoy our first meeting, we can discuss an ongoing coaching relationship that’ll help you reach your Front-End development goals!
Thanks for reading
For more content like this, check out https://yazeedb.com!
Until next time!