KA Engineering

KA Engineering

We're the engineers behind Khan Academy. We're building a free, world-class education for anyone, anywhere.

Subscribe

Latest posts

Let's Reduce! A Gentle Introduction to Javascript's Reduce Method

Josh Comeau on July 10

Creating Query Components with Apollo

Brian Genisio on June 12

Migrating to a Mobile Monorepo for React Native

Jared Forsyth on May 29

Memcached-Backed Content Infrastructure

Ben Kraft on May 15

Profiling App Engine Memcached

Ben Kraft on May 1

App Engine Flex Language Shootout

Amos Latteier on April 17

What's New in OSS at Khan Academy

Brian Genisio on April 3

Automating App Store Screenshots

Bryan Clark on March 27

It's Okay to Break Things: Reflections on Khan Academy's Healthy Hackathon

Kimerie Green on March 6

Interning at Khan Academy: from student to intern

Shadaj Laddad on Dec 12, 2016

Prototyping with Framer

Nick Breen on Oct 3, 2016

Evolving our content infrastructure

William Chargin on Sep 19, 2016

Building a Really, Really Small Android App

Charlie Marsh on Aug 22, 2016

A Case for Time Tracking: Data Driven Time-Management

Oliver Northwood on Aug 8, 2016

Time Management at Khan Academy

Several Authors on Jul 25, 2016

Hackathons Can Be Healthy

Tom Yedwab on Jul 11, 2016

Ensuring transaction-safety in Google App Engine

Craig Silverstein on Jun 27, 2016

The User Write Lock: an Alternative to Transactions for Google App Engine

Craig Silverstein on Jun 20, 2016

Khan Academy's Engineering Principles

Ben Kamens on Jun 6, 2016

Minimizing the length of regular expressions, in practice

Craig Silverstein on May 23, 2016

Introducing SwiftTweaks

Bryan Clark on May 9, 2016

The Autonomous Dumbledore

Evy Kassirer on Apr 25, 2016

Engineering career development at Khan Academy

Ben Eater on Apr 11, 2016

Inline CSS at Khan Academy: Aphrodite

Jamie Wong on Mar 29, 2016

Starting Android at Khan Academy

Ben Komalo on Feb 29, 2016

Automating Highly Similar Translations

Kevin Barabash on Feb 15, 2016

The weekly snippet-server: open-sourced

Craig Silverstein on Feb 1, 2016

Stories from our latest intern class

2015 Interns on Dec 21, 2015

Kanbanning the LearnStorm Dev Process

Kevin Dangoor on Dec 7, 2015

Forgo JS packaging? Not so fast

Craig Silverstein on Nov 23, 2015

Switching to Slack

Benjamin Pollack on Nov 9, 2015

Receiving feedback as an intern at Khan Academy

David Wang on Oct 26, 2015

Schrödinger's deploys no more: how we update translations

Chelsea Voss on Oct 12, 2015

i18nize-templates: Internationalization After the Fact

Craig Silverstein on Sep 28, 2015

Making thumbnails fast

William Chargin on Sep 14, 2015

Copy-pasting more than just text

Sam Lau on Aug 31, 2015

No cheating allowed!!

Phillip Lemons on Aug 17, 2015

Fun with slope fields, css and react

Marcos Ojeda on Aug 5, 2015

Khan Academy: a new employee's primer

Riley Shaw on Jul 20, 2015

How wooden puzzles can destroy dev teams

John Sullivan on Jul 6, 2015

Babel in Khan Academy's i18n Toolchain

Kevin Barabash on Jun 22, 2015

tota11y - an accessibility visualization toolkit

Jordan Scales on Jun 8, 2015

Meta

Let's Reduce! A Gentle Introduction to Javascript's Reduce Method

by Josh Comeau on July 10

Every summer, Khan Academy recruits a few software engineer interns. As part of their onboarding, we host several brief talks introducing the technology we work with: React, Flow, Google AppEngine, and so on. I volunteered to introduce Redux, a tool to manage front-end state.

For this reason, I found myself looking at how Redux is taught. There are a lot of great resources for learning Redux, but few of them cover the fundamental knowledge you need to fully understand it. The more I thought about it, the more I realized that to understand Redux, you first need to understand Array.prototype.reduce.

reduce is an array method, similar in spirit to map and filter, but far more flexible. Like those other methods, reduce is called on every item in the list, but the end result can be whatever you need; an array of data, an object, a number.

That flexibility comes at a price, though: it's pretty tough to get the hang of. Unlike those other array methods, each iteration in reduce is affected by the previous iteration's return value. Similar to recursion, you need to be able to keep track of the sequence of iterations in your head, and this can be a hard skill to develop. It's like building muscle memory; it takes a while for it to become intuitive and natural.

I had initially set out to write a blog post introducing Redux, but reduce warrants a blog post of its own. It's foundational to understanding Redux, and it's also a really neat tool to have in your toolbelt.

Prerequisite Knowledge: I’ll be assuming you have an understanding of modern Javascript, including arrow functions, enhanced object literals, and the array spread operator. It’s probably also helpful if you know about Array.prototype.map and Array.prototype.filter. Once you’ve gotten that stuff down, come on back.

Some Words That Probably Won’t Help

Every explanation I’ve seen for reduce tries to explain how it works using English.

The problem is, it’s a bit like trying to learn how to tie a shoelace using words alone; you may be able to understand intellectually how it works… but good luck keeping your shoes on.

I expect that the “ah-ha” moment will come further on, when looking at a visualization of how it works, or trying the problem exercises for yourself. Nevertheless, I think it’s worthwhile to have these ideas grasped loosely in your mind, the soil from which understanding will grow.

First, a definition:

reduce is a way of deriving a single result from a series of values.

A suitable analogy is assembling a layer cake. We start with a bunch of layers, and each layer needs to be frosted and assembled, producing a single cake at the end.

Summing Values

The most common example for explaining reduce is using it to sum an array of numbers. Say you have the following data, and you want to calculate the total:

const arr = [3, 5, 1, 4, 2];

reduce allows you to take this array of numbers, and compute a single result (in this case, the number 15).

There are, of course, many ways of solving this problem. Here’s an example that uses a forEach loop:

let total = 0;

arr.forEach((item) => {
  total += item;
});

In this solution, we iterate through our list, adding each value to a variable kept in the parent state.

reduce works in a similar way, but doesn't involve mutating an outside variable. Here's how we'd do it:

const arr = [3, 5, 1, 4, 2];

const total = arr.reduce((acc, item) => {
  return acc + item;
}, 0);

Right away, we can see that there are some similarities; you call it on an array, and then it calls a provided callback function on every item in that array.

reduce takes two arguments:

  • the callback function, called once for every item in the array. This callback takes a parameter that we'll call acc (short for accumulator; this name will make sense soon), and the item itself.
  • an initial value.

That initial value becomes the first argument supplied to the callback as acc. Our initial value is set to be 0, and the first item in our array is 3, so on the first iteration, our function body populates to return 0 + 3.

For every subsequent iteration, acc receives the previous iteration's return value. This is the tricky part of reduce. We named the parameter acc, because it's like a snowball rolling down a hill; it accumulates the value of each iteration. This isn't true for all reduce functions, but it's a good analogy in this case.

Following the sequence, the second iteration receives 3 as the acc parameter (since the first iteration returned 0 + 3), and it adds that value to the next item in our array: return 3 + 5;.

The third iteration receives 8 as the acc (since 3 + 5 is 8), and returns 8 + 1.

This process continues until the final iteration, where it returns 13 + 2, which resolves to our final answer, 15.

Visualize it

As I said earlier, I don’t expect these words to work miracles.

The hardest part of learning reduce is developing an intuitive understanding of how data flows through it from iteration to iteration.

This visualization showcases the above example of adding values. Click the GIF to view it in its entirety:

Visualization of the Reduce process

View the visualization



Play with it

The best way to solidify understanding is to actually do it. I created a JSBin with the sample reduce summing code; poke around with it! Sprinkle some console.logs around to see what the variables hold. Try messing with it, and see what happens.

Another Example

Because reduce gives you full control over its output, it's extremely flexible, and not just used for summing numbers.

Let's look at a common data-wrangling concern. Let's say we have an array of user objects, and we want to create a map-like object. This is actually a pretty common problem, as libraries like Redux advocate storing data in a database-like tree structure:

// Let's say our data comes back from
// the API as an array of objects:
const inputFromServer = [
  {id: 'a', name: 'Amy'},
  {id: 'b', name: 'Blanche'},
  {id: 'c', name: 'Claude'},
];

// We'd like to create a map-like object:
const desiredOutput = {
    a: {id: 'a', name: 'Amy'},
    b: {id: 'b', name: 'Blanche'},
    c: {id: 'c', name: 'Claude'},
};

We can't simply use map here, because we want to return an object, not an array. reduce to the rescue!

const getMapFromArray = data => (
  data.reduce((acc, item) => {
    acc[item.id] = item;

    return acc;
  }, {})
);

getMapFromArray(inputFromServer)
// returns an object identical to `desiredOutput`.

Our initialValue is an empty object, and on each iteration, we augment it with the item provided. Each item in the array is added to acc object, keyed by its id.

Practice: recreate map and filter with reduce

By now, it should be clear that reduce is an extremely flexible method, compared to map or filter.

You may be surprised to learn, though, that map and filter can be reimplemented with reduce.

Your challenge, should you choose to accept it: Create map and filter functions that replicate the native functionality, but using reduce internally.

Here are some JSBins that set this up. Good luck!

Implement map

Implement filter

Stuck? You can view the solutions here. Do your best to figure it out before peeking, though!

Using the right tool for the job

reduce is a very handy hammer, but not every problem is a nail.

For example, you might think that this is a perfect problem statement for reduce:

Given an array of values, filter out
all negative values, and double all
remaining values.

eg. [2, -4, 6] -> [4, 12]

This is totally solvable by reduce! We can write a little function that just returns the acc if the item is negative, and pushes in a doubled item if it's positive!

const positiveDoubler = data => (
  data.reduce((acc, item) => {
    if (item < 0) {
      return acc;
    }

    return [...acc, item * 2];
  }, [])
);

This works, but this function is really doing two different things. Why not break it up into discrete steps?

const isPositive = item => item >= 0;
const doubleItem = item => item * 2;

const positiveDoubler = data => (
  data
    .filter(isPositive)
    .map(doubleItem)
)

I think most would agree that this solution is clearer.

The moral of the story? reduce is awesome, but sometimes there are simpler solutions. Always strive to write code that others can easily understand.

Shorthand Syntax

Javascript's implementation of reduce offers a convenient shorthand.

I didn't mention this earlier, because I wanted to strip away all the non-essential bits before covering this little bit of syntactic sugar. In fact, if you still don't feel like you have a firm grasp on reduce, I'd advise skipping this section for now. It's not necessary knowledge to use reduce.

The shorthand allows you to omit an initialValue. If you do, reduce uses the first two values in the array as the first two parameters in your callback.

Let's look again at our summing example:

const arr = [3, 5, 1, 4, 2];

const total = arr.reduce((acc, item) => {
  return acc + item;
});

If no initialValue is provided, then acc takes on the value of 3 and item takes on the value of 5. For this example specifically, everything still works as-intended, but there are only 4 iterations instead of 5.

Note that this is not always the case; Our data-wrangling example needs the initial value of {}.

Conclusion

Phew! You made it.

If reduce still doesn't feel like it's totally sunken in yet, don't worry. Keep practicing! It's worth the effort.