The beauty of pure functions

Pure functions form the building blocks of functional programming and help a lot in not repeating yourself and making code easier to maintain. Let's take a close look and figure out what makes them so awesome. At its core pure functions will

  • Always return the same output, given the same result
  • Have no side effects (mutating, logging, writing to disk, ...)

Here's a very simple example

const add = function(x, y) {
	return x + y;
}

Given the same x and y, the function will always return the same result and it has zero side effects, because it doesn't fo anygthing but add the two variables.

An impure function on the other hand could look like this:

let sentence = "Hello ";

const addWord = function(word) {
  sentence += word;
};

addWord("World"); // x === Hello World (only the first time!)

Because it modifies a variable that is out of its scope, the function will return a different result the second time it's called, even if we pass in the same word. The function relies on a shared state, because the variable "sentence" can change outside the function, which makes it non-deterministic.

Those basic examples already illustrate why pure function can be easier to reason about and write test for and why some functions that are impure can be an absolute nightmare to debug.

Side effects

The most important part of "no side effect" boils down to "don't alter any outside state". For example, passing an object into a function, pure functions would only modify a clone of that object and never the original object.

const alterAnimal = (key, value, object) => {
	// We're cloning the passed object, so we can be sure not to alter
  // the original, which would make the function impure.
  const clone = { ...object }; 

  clone[key] = value;

  return clone;
};

const animal = {
  name: 'Baloo'
};

const result = alterAnimal('category', "cat", animal);

// Because we haven't modified the passed object directly, 
// result now holds an additional property that animal doesn't have
console.log({
  animal,
  result
});

Only if we use a (deep) clone instead of the actual animal object, we can guarantee repeatable results and easier testable and understandable code. Altering variables outside of our function's state on the other hand makes it a lot harder to understand what's going on and potentially send us down a rabbit hole when looking for bugs.