Writing resilient, unbreakable code using functional patterns
Quality of an abstraction depends on the quality of its units

Fresh grads, recruits from coding camps, ex-Java/Perl/Ruby/Python coders tend to write in an imperative style. There is nothing wrong with it, I was in the same boat. I remember sometimes certain data transformation functions were so nested with conditional logic strewn through out, I couldn’t reason my own code after a few months. Debugging was a nightmare. Then I read up on functional programming and it changed the way I wrote code.
An example
We’re going to write a simple function that takes in an array of numbers and return anything less than 7 as a new array of formatted strings. If your function looks something like this, you are not alone.
const earlyHours = (hours) => {
const early = [];
for (let i = 0; i < hours.length; i++) {
if (hours[i] < 7) {
early.push(`${hours[i]} am`);
}
}
return early;
};console.log(earlyHoursImp(hours));
// returns ["1 am", "2 am", "3 am", "4 am", "5 am", "6 am"]
It works. What is the issue then?
It is imperative. You are telling the computer explicit instructions to do this, add this, do that and so on. Imperative code is rife with potential error points. and it relies on state mutation. earlyHours() function needs two temporary state values to do its computation., both are mutated many times. “i” is initialized and incremented in every loop. “early” is also initialized and built out in increments. Check the image below. Note the parts annotated by explosions and underlines.

There are about seven potential areas for bugs here.
Pure functions
First step in using a functional style is to learn to write pure functions. A pure function has the following properties:
- They are 100% predictable. For a given a set of input arguments, the return value is always the same.
const sum = (a, b) => a + b;
- It will not access or mutate anything outside its scope.
// These are not cool inside a pure function
locaStorage.getItem("name");
locaStorage.setItem("name", "me");
fetch("/api/items");
window.addEventListener(...
location.assign(...
- It will not mutate its function arguments.
// do not modify the input values
const getNewOnes = list => {
list.forEach(list => list.id = "id-" + list.num);
return list.filter(list => list.num > 5);
}
- Return values are always fresh copies of data. This means that you cannot return an object that still refers to properties inside the input arguments
// person = {
// name: "Me",
// address: { "city": "San Jose", state: "CA"}
// }const getAddressTheWrongWay = person => {
return { address: person.address };
// address is pointing to the argument's property
}const getAddressTheRightWay = person => {
return { address: {...person.address} };
// spread operator clones the properties
}
Using pure functions as the basis of your design brings an important quality to your design: immutability. This means that mutations will occur only where explicitly specified, there will not be any accidental ones.
Functional style
When it comes to data transformations of lists, there are three methods we must master. map, filter and reduce. Our function can be rewritten as follows:
const earlyHours =
(hours) => hours.filter(h => h < 7).map(h => `${h} am`);
There are no temporary state variables here and only two potential points for bugs, one in the comparison and the other in the template string.
Functional tools such as map and filter are time tested, iteration is handled internally. Also remember, forEach is still imperative because to be effective, it will need to mutate state outside its scope.
Code Surface area
For fun, I took screenshots of both functions on codepen and measured how much space they took.
Imperative: 473 x 245 = 115885 pixelsFunctional: 890 x 30 = 267000 pixels
Using a functional style, you are able to reduce the code surface to 23% of the original. The less code surface you have, the lower chances of bugs.
Going further in improving code quality
But is it enough to just adopt better patterns? What else can you make your function unbreakable?
Add input validations
What if the input to the function is invalid? The function will crash. In this example, you can use JavaScript’s optional chaining to protect from bad inputs.
// returns undefined if input is not an array
const earlyHours =
hours?.filter?.(h => h < 7).map(h => `${h} am`)
Add default values to function arguments
If applicable, you should use default values for function arguments. This can remove unnecessary null/undefined checks most of the time.
const earlyHours =
(hours = []) => hours?.filter(h => h < 7).map(h => `${h} am`);
Return meaningful empty values
I recommend returning meaningful empty values such as ``, {} or [] instead of null and undefined. This means that the caller of the function can safely use string and array functions without having to worry about undefined error.
const earlyHours =
(hours) => hours?.filter(h => h < 7).map(h => `${h} am`) || [];
Unit tests
Hopefully your projects are strapped with a test framework. You can unit test pure functions very easily.
// using expect BDD assertions
expect(earlyHoursImp([1, 2, 3])).to.eql(["1 am", "2 am", "3 am"])
You can add tests for several more scenarios like these.
Static Typing
Static typing with TypeScript can prevent your function from being invoked improperly.
const earlyHours =
(hours: number[]) => hours.filter(h => h < 7).map(h => `${h} am`) || [];
This makes it impossible to call the function with anything other than an array of numbers. TypeScript is not present during runtime thus will not protect you from type incompatibility.
You can play with the CodeSandbox here: https://codepen.io/rajeshnaroth/pen/WNrjybX
Use a functional utility library
Beyond map, reduce and filter there are several utilities that can help you with a functional style of programming. lodash and ramda are two very popular libraries. I like ramda for its API design. As homework, I recommend you look into functions such as compose, pipe, flatten, pick, find etc.
Imperative code is essential, otherwise you will just have a static site. Your application will need to go from one state to another based on user/browser/network events. What is great about JavaScript is that it supports both styles.
Summary
- Write pure functions
- Use functional patterns such as map/reduce/filter/compose
- Add input validations.
- Use defaults in arguments
- Return empty objects instead of null or undefined
- Write unit tests
- Use static typing
To build high quality abstractions, build them with resilient, compose-able units. These tips can help you achieve that.