Posts Pure Functions in JavaScript
Post
Cancel

Pure Functions in JavaScript

I’ve been busy practicing my problem solving technique. When I looked at the study guide for the assessment, I noticed a line about pure functions and side effects. I decided to dig into this some more so I’m ready for the assessment.

If a function always returns the same value when the same arguments are passed in, regardless of surrounding code or the state of external variables, and the function has no side effects, the function is said to be a pure function. For example:

1
2
3
function appendTest(word) {
  return `${word}test`;
}

The return value of this function will always be the same given the same string:

1
2
3
console.log(appendTest('Mine'));  // Always returns 'Minetest'
console.log(appendTest('Test'));  // Always returns 'Testtest'
console.log(appendTest());        // Always returns 'undefinedtest'

Pure functions like this are easier to test since they tend to be isolated from the rest of the program.

This function also doesn’t have side effects. It doesn’t:

  • reassign a non-local variable
  • mutate the value of any object referenced by a non-local variable
  • read from or write to any data entity (files, network connections, etc…) that is non-local to the program
  • raise an exception
  • call another function that has side effects

No other code in the program will alter the return value for a given string.

Reassign Non-Local Variable

This is a side effect where the function reassigns a variable that was declared outside of the function. For example:

1
2
3
4
5
let fullName = 'Mia';

function addLastName(lastName) {
  fullName = `${fullName} ${lastName}`;
}

In this example, the variable fullName is declared in the outer scope and then reassigned from within the inner scope of the addLastName function. If the fullName variable is reassigned or changed in another part of the program, the addLastName function may reassign the fullName variable in an undesirable way.

1
2
3
4
5
6
7
8
9
10
11
let fullName = 'mia';

function addlastname(lastname) {
  fullName = `${fullName} ${lastname}`;
}

addLastName('Sunji');
console.log(fullName);  // Returns 'Mia Sunji'

addLastName('Sunji');
console.log(fullName);  // Return 'Mia Sunji Sunji'

We call the addLastName function and pass in the same string Sunji as an argument in two method calls. If this were a pure function, we would expect that the return value would be the same for both function calls and that nothing else is changed outside of the function. However, in this case, the fullName variable is reassigned from within the function making the return value different depending on when in the program the function is called and what the value of fullName is at that time.

Mutate the Value of Any Object Referenced by a Non-Local Variable

This is a side effect where the function mutates the value of an object that’s referenced by a variable declared outside of the function.

1
2
3
4
5
6
7
8
9
10
11
let fullName = ['Mia'];

function addLastName(lastName) {
  fullName.push(lastName);
}

addLastName('Sunji');
console.log(fullName);  // Returns ['Mia', 'Sunji']

addLastName('Sunji');
console.log(fullName);  // Returns ['Mia', 'Sunji', 'Sunji']

This is similar to the reassignment side effect, but instead of reassigning the variable in the outer scope, the function mutates the object that the variable references. When someone reads this code, it can be difficult to tell that the fullName variable declared in the outer scope is mutated from the addLastName function call, making this type of side effect more cumbersome to troubleshoot.

Read From or Write To Any Data Entity That is Non-Local to the Program

The function is said to have a side effect when it causes JavaScript to look outside of the program to read and/or write or send data. This can include any type of input/output operation, from reading from a file to accessing a database to accessing the mouse or camera. Even using the system random number generator via Math.random() or the system clock via new Date() is looking outside of the program to read and/or send data.

For example, logging to the console is a side effect:

1
2
3
function sayHi() {
  console.log('hi');  // output to console is side effect
}

Raise an Exception

Throwing an exception without handling it within the function is a side effect.

1
2
3
function sayHi() {
  throw new Error('Hello error!');  // raising an exception is a side effect
}

Call Another Function That Has Side Effects

This side effect is when a function calls another function that has a side effect that’s visible outside of the calling function. The example above using console.log is an example of this. The log method has a side effect of logging to the console and therefore, so does the sayHi function.

Calling functions within a function that have side effects but that are not visible outside of the calling function are technically not side effects of the calling function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function isWordHello(word) {
  return word === 'hello';
}

function isHelloPresent(array) {
  let arrayOfWords = array.slice();
  arrayOfWords.shift();
  return arrayOfWords.filter(isWordHello).length > 0;
}

let array1 = ['no', 'it', 'is', 'not'];
let array2 = ['yes', 'it', 'is', 'hello'];

console.log(isHelloPresent(array1));  // Always returns false
console.log(isHelloPresent(array2));  // Always returns true

console.log(array1);                  // [ 'no', 'it', 'is', 'not' ]
console.log(array2);                  // [ 'yes', 'it', 'is', 'hello' ]

In this example, the array that gets passed into the isHelloPresent function is copied and then the first element is removed from the copied array. Normally, if we call shift on an array, the calling array is mutated. That still happens in this example, but because we are calling shift on a copy of the array, the array declared in the outer scope is not affected. We can see that the array that’s passed into isHelloPresent as an argument is not mutated, so the isHelloPresent function does not have any side effects and is a pure function.

Mixing Side Effects and Return Values

In general, it’s best to either return a value or have a side effect, but not both. What’s a useful value? It’s a value that is not arbitrary and not the same for each function call:

1
2
3
4
5
6
7
8
9
10
11
function isNotUseful(word) {
  console.log(word.toUpperCase());
}

function isUseful(word) {
  console.log(word.toUpperCase());
  return word.toUpperCase();
}

isNotUseful('hello');  // Always returns undefined, so it's not a useful return value
isUseful('hello');     // Returns the given word with all uppercase characters

In the first function, isNotUseful, the return value of that function will always be undefined. Therefore, this function does not mix a useful return value with the console.log side effect.

In the second function, isUseful, the return value is different depending on the word and that word is logged to the console. This function mixes a useful return value with a side effect.

This is a general rule that has some exceptions. For example, it’s OK for a function to read from a database or get input from the keyboard and return a useful value.

I’m not yet sure how this will be applied to an interview assessment, so we’ll see. Hopefully the above information is enough for me to fully understand these concepts and pass the assessment.

This post is licensed under CC BY 4.0 by the author.