Pure Functions in JavaScript

And how can we profit from using them (5 mins including Redux example)

Igor Łuczko
5 min readAug 2, 2020

Pure Functions is one of these terms that yet again sound very elitist and difficult to understand. In fact it’s rather simple concept.

The pure function is very commonly used by lot’s of major players i.e. Redux, React, Angular and so on. We can also extract expensive pure function to a web worker and increase performance of our application. There are plenty of potential use cases.

It’s also one of the programming concepts that’s not JavaScript unique. It’s also very common interview question.

As such it’s a must have in every developer toolbox.

In this article I will explain the concept with a bit of help from easy to understand examples.

Pure functions

A simple definition is that a pure function is a function which:

  • Given the same input, will always return the same output.
  • Produces no side effects.

So what does it mean for us?

1. Given the Same Input, Always Return the Same Output

We will start with classic example: the Math.random() function.

Math.random(); // => 0.7051146457320575
Math.random(); // => 0.6456886404688536

As we can see, the first condition Given the same input, will always return the same output is not met.

We have called the function twice and got different results for each call.
That is why we have to say that Math.random() is not pure function.

Let’s look at a different example.
We have a function addTwoNumbers which we can inspect below:

const addTwoNumbers = (a, b) => a + b;const resultFirstCall  =  addTwoNumbers(10,20) // 30
const resultSecondCall = addTwoNumbers(10,20) // 30
const resultThirdCall = addTwoNumbers(10,20) // 30
const resultFourthCall = addTwoNumbers(10,20) // 30

We could run this function how many times we like, but every time we pass it 10and 20 as an input, we will always get 30 as an output.

For the sake of above example, we will leave the famous cases like 0.1 + 0.2 === 0.3 out of the equation.

The function also does not trigger any side effects (more on that in the second paragraph).

Both of the conditions are met, hence our addTwoNumbers = (a,b) => a+ b; is a pure function.

There is also a rule of thumb that can help us.
During my work I’ve noticed that:

If it makes sense to call a function without using its return value, the function is very, very likely not pure.

2. The function produces no side effects.

If you work with React, Vue, Angular or some state manager i.e. Redux you definitely have heard about side effects and immutability.

The same concept applies here.

In simple terms, the no side effects condition means that pure function must not change any external state.

A basic example to help us understand the problem:

let a = 5;// some other codeconst addNumber = number => {
a = 100;
return number + a;
}
console.log(addNumber(5)); //105
console.log(a); //100

The function addNumber()changes the value of external variable a, which is outside of it’s scope (that’s the side effect our function is performing).
This means that the function addNumber() in this form is not pure.

By the way, this example breaks a lot of good practices but it’s just to help us understand the side effects case.

Don’t write your code like that.

3. Pure functions in Redux

The Redux is a state-container tool. In this article I will not talk about Redux much, I will just show an example of a reducer being a pure function.

const initialState = {
myDataArray: [],
};
const dataReducer = (state = initialState, action) => {

let newState;

switch (action.type) {
case CONSTANTS.GET_MY_DATA_SUCCESS:
newState = { ...state, myDataArray: action?.payload?.data };
break;
default:
newState = state;
}
return newState;
};

This is very simple example, but it’s good enough to state the case about pure functions.
In real life we would use maybe immer and our code would perform more checks and so on.

Let’s check if we met pure functions conditions

  • given same input (same data) the reducer will return same output.
  • We create new state object and as such we don’t mutate the original state, instead we return a new state object. As such there are no side effects.

This means that above reducer is a pure function. As a side note, the reducer must be a pure function to prevent us from bugs.

In short, the reducers in Redux:

  • given the same arguments, should calculate the next state and return it.
  • don’t do side effects;
  • don’t do API calls;
  • don’t do mutations;
  • as a rule, the reducers must not surprise us.

4. The benefits of pure functions

There is a lot of benefits coming from using pure functions.

Because given same input, the pure function produces same output (and no side effects) it’s rather clear to understand what the function is doing. Sometimes we can just look at the signature or the comments and it’s enough.

The top benefits coming from pure functions are:

  • It’s rather clear what they are doing
  • Rather easy to understand for other devs and for your future self
  • Easier to unit tests them
  • Easier to debug them
  • They can be memoizable
  • They can be lazy
  • Easier to “parallelize” (we can extract expensive pure function to a web worker so that the function won’t block the main thread)
  • And other

A word of caution about functions

There is a tax however —it’s easy to fall in love with functions and start overusing them.

If your functions will become too small and too neat, pretty soon your codebase can become a spaghetti.

At that point you will have a lot of neat reusable helpers. To understand the code you will have to jump from function to function to another function just to understand a simple feature.

Consider this example:

import { pureHelper1, pureHelper2 } from 'helpers';function foo(param1, param2) {  // suppose we forgot what pureHelper1 is doing
// we have to navigate away the file
// there is a possibility that pureHelper1 calls another helper
const response1 = pureHelper1(param1);
const response2 = pureHelper2 (param2);
const finalResponse = response1 + response2; return finalResponse;
}

In the wild, I’ve seen a lot of functions. In order to understand what some of them were doing, I had to inspect tens of different helpers.

And let’s not forget about the changing requirements — suppose one small tiny thing will change — I will leave it with you to have a think about that problem, we’ve all been there (or will be).

My advice is to keep the codebase as simple as possible (within reason of course).

You will know when to abstract the logic to another function when you will need it.

Your turn

We now have an understanding about the pure functions.

We understand the benefits, we are also aware about the risk coming from this pattern.

With that knowledge, a good exercise would be to create a pure function that sorts some array of data and extract it maybe to a web worker (or AWS Lambda maybe?).

There is so much we can do now. :)

--

--

Igor Łuczko

Technical Lead. Seeking to make a change. Doing quality work that matters for people who care.