Functional Programming in JavaScript

Josue Alpizar
15 min readMar 22, 2021

--

Functional programming (often abbreviated FP), is just another programming paradigm, like object-oriented programming and procedural programming. Meaning that it is a way of thinking about software construction, with new concepts, techniques, and principles that must be followed. Of course, we are going to explore them later in this post. But for now, don’t let all the new concepts scare you away.

FP is a declarative paradigm, in which the code is written without explicitly describing the flow control, and that comes with a lot of benefits. Some of them are that FP produces code more concise, easier to debug, test, and more scalable.

There are languages that have a strong functional programming orientation like Clojure and Haskell. However, Javascript is not a fully functional programming-oriented language. However, I going to show you in this post how we could have FP in JavaScript and most importantly show the benefits it represents.

Indeed, JavaScript is a multiple paradigm language. Yeah, I know, JavaScript is great.

Besides that, FP is a hot topic since you need to understand it, if you really want to master libraries like React.js and Redux.js.

In a nutshell, the main idea is to derive a certain problem into several small and reusable functions, and then compose them in order to resolve problems. These functions must follow these principles used in FP to build software: function composition, currying, pure functions, mutable and immutable data, and avoiding shared states, among others but these are the more important ones.

If you want to know what functional programming means in practice, you have to start with a solid understanding of these concepts. So, let’s explore them and let me give you some techniques to work with them in the JavaScript world.

High order functions

Are those functions which take functions as arguments and return a function or both. These functions are extremely powerful, and we have been working with them, even without being aware of them, for example:

var numbers = [1,2,3,4];
var calculatedNumbers = numbers.map(number => number *2);
setTimeOut(function(){
console.log(calculatedNumbers);
});

The map and setTimeOut functions are high-order functions, both take a function as an argument and do something with it inside. The same applies to those functions that return a function. An important thing about high-order functions is that they are generic since JavaScript has first-class functions, which allows us to treat functions as data. We could pass a function as an argument, the internal implementation could be the whole world if we want. This technique favors generics implementation since it is not tightly coupled to a specific data structure.

Function composition

Fundamentally, composition is the process of combining more than one function and create execution pipelines wrapped in a new returning function. The easiest way to explain this is by using an example. First, imagine we need to create a program that takes a string and returns it: trimmed, uppercase, and wrapped into an HTML span tag.

The common way to resolve it would be:

function getHtmlSpanInUpperCase(str){
return `<span>${str.trim().toUpperCase()}</span>`;
}

But, remember that in functional programming we want to derive our solution into small and reusable functions, and then apply the composition to create an execution pipeline. Therefore, the solution would be:

var trim = str => str.trim();
var toUpperCase = str => str.toUpperCase();
var wrapIntoSpan = str => `<span>${str}</span>`;
console.log(wrapIntoSpan(toUpperCase(trim("JavaScript"))));

This is a very simple example. However, be creative. The same approach can be used to resolve any requirement, just think about all possible use cases of this. And notice that, now these functions are easier to test, easier to maintain, but most importantly, they are reusable.

Creating pipes

As I mentioned before, JavaScript is not a fully functional programming language. So, maybe you noticed that ugly function nesting and all the parenthesis is too verbose. That was a very simple example, but think about when we have large function compositions, it will be a mess, an open door to code smells.

So in order to work properly with function composition, in JavaScript, there is a popular library that can help us. Lodash is a modern JavaScript utility library, it is extremely powerful and I really encourage you to check out its documentation later.

Lodash has a function named pipe, which allows us to create function composition very easily. Accordingly, to our last example, the solution using Lodash would be:

import {pipe} from 'lodash';const transform = pipe(trim, toUpperCase, wrapInSpan);transform("JavaScript");

Very simple, very organized, very beautiful. When we use the Lodash pipe function The execution order reads from left to right, meaning that trim is going to be called first, then toUpperCase, and finally wrapInSpan. And also, the argument passed to toUpperCase is the returned value of trim, and the argument passed to wrapInSpan is the returned value of toUpperCase, and so on. The argument passed to trim is the one we passed when called the transform function at first. Of course, if you change the order of the composition, the output will change.

In function composition order of operations matters.

This is how we could create pipelines using Lodash, and of course, you can create your own pipe function and remove the Lodash dependency. However, just be aware that you are not reinventing the wheel.

Currying

This has nothing to do with food, it is about Haskell Curry. We owe him this technique called currying. So far, all our functions are taking just one argument, and the returned result of the function is being passed as an argument to the next function in the pipeline. But what would happen if you have functions that take n arguments?

Well, currying comes to the rescue. Fundamentally, currying allows us to pass arguments separated by parentheses rather than commas. But, multiple things happen under the hood while using this technique. Let me show you what I mean by that.

Using the same example below, let’s say now we need to wrap the string into any HTML tag, not just span tag, but wouldn’t be a fancy approach if we create a function for each HTML tag right? Instead, we can create a function that takes the tag name as an argument!

var wrapInHtmlTag = (str, tagname) => `<${tagname}>${str}</${tagname}>`;

That function works perfectly. However, in function composition, all the arguments after the first argument in functions used as part of a composition, are going to be undefined. Why? Well, that happens because the returned value of the previous function is mapped to the first argument of the preceding function, and all the resting arguments don’t have an associated mapping.

Therefore, we must provide those arguments explicitly:

import {pipe} from 'lodash';const transform = pipe(trim, toUpperCase, wrapInHtmlTag("div"));transform("JavaScript");

But now, we are facing another issue, our pipe function can only take function type arguments, and we are passing a string type in our last step. Because we are invoking the wrapInHtmlTag function that returns a string.

So, how can we resolve that? Well, return a function instead! But how? Well, we use currying. In order to get that pipeline working properly, we need to currying the wrapInHtmlTag function. Let me show how:

var wrapInHtmlTag = str => tagname =>`<${tagname}>${str}</${tagname}>`;

If you are not familiar with arrow functions, this would be the according to implementation using normal functions:

function wrapInHtmlTag(str){
return function(tagName){
return `<${tagName}>${str}</${tagName}>`;
}
}

So, all I did was to return a function in my current wrapInHtmlTag and each internal function can only take one argument, and finally use all the nested arguments in the last defined function to compute something, it is easier than it sounds right? I want you to notice what happened after we applied currying:

  1. We are able to use the wrapInHtmlTag function in our pipeline because it returns a function rather than a string.
  2. Therefore, we are now working with a high order function
  3. We are now able to use functions that take more than one argument in our pipelines

Remember that I told you before that currying allows us to pass arguments separated by parentheses rather than commas? That is exactly what is happening when calling the wrapInHtmlTag(“div”) in the pipe function we declared before. Because we are now able to do something like this:

console.log(wrapInHtmlTag("javascript")("span"));

Do you see? We are not calling the function as we usually do, the arguments are being passed separated by parentheses. Under the hood, when the console.log executes, the control calls the wrapInHtmlTag only with “javascript” as an argument, and immediately when that execution ends, the control calls the returned function with “span” as argument producing the result. The same mechanism happens inside pipelines using the Lodash pipe function. If you are a ninja developer, you have noticed that currying is true, the combination between high order functions and function composition.

Pure Functions

In functional programming, almost everything is about pure functions. Is the paradigm’s foundation. And they are those functions that take the same arguments, always return the same result, and have no side-effects. Let’s explore this, step by step. First, same arguments, same result principle, take a look at add2 function below:

function add2(number){
return number +2;
}
add2(2); // 4
add2(10); // 12

No matter what, if we pass 2, we always are going to get 4 as result. It doesn’t matter how we call this function always the same argument is going to produce the same result.

Maybe, you are thinking: “that’s a very simple function is not the real world”. Well, that’s the main objective of pure functions. Always try to follow the KISS principle in your pure functions: Keep it simple. They are the simplest reusable building blocks of code in a program. Pure functions are stupid-simple in the best possible way.

Side effects

Since we should create our pure functions in the simplest possible way. We should take care of isolate them and be independent of external resources and processes. A side effect is any change that is observable inside the called function other than its return value, mostly external resources. Some side effects include:

  • Modifying any external variable or object property (e.g., a global variable, or a variable in the parent function scope chain)
  • Writing to a file
  • Triggering a network operation
  • Triggering any external process
  • Calling any other functions with side-effects

We need to isolate all the side effects to be decoupled components. Pure functions are completely independent of the outside state, and as such, they are immune to entire types of bugs that have to do with a shared mutable state (don’t worry about mutable, we are going to talk about this soon). Imagine them as isolated small pieces responsible for a single task with no side-effects of outside states.

Benefits of pure functions

Their independent nature also makes them great candidates for parallel processing, easy to move around, refactor, and reorganize in your code, making your programs more flexible and adaptable to future changes. Due to its nature, pure functions should be self-documenting and easy to test.

The most important advantage of pure functions is that they are cacheable. Meaning that, since pure functions always produce the same return from the same arguments we could cache that returned value after the first execution and returning it without actually calling the function again with the same argument. This is extremely powerful, libraries like Redux and React use this feature a lot, this is the magic under the useCallback and useMemo hooks.

So, keep these characteristics in mind. Pure functions are extremely important in FP. There several guidelines to work with pure functions, let me show you the most common and important:

  1. Same arguments, same result. Internally, we cannot use random values, current date, global values, or more specifically, values the can chance easily at runtime. For example, if you have a function that computes the milliseconds’ difference between a specific date and a current date. The current date is always going to be different. Therefore the result always is going to be different from the same argument.
  2. Pure functions are self-documenting
  3. Pure functions cannot mutate the arguments, they should remain pure
  4. Pure functions are simple, simplest as possible.
  5. Pure functions have no side-effects regarding the outside state.
  6. All required data for a pure function to compute something must be passed as arguments when don’t go outside of the function in order to get that data.

If you are interested of get a deeper and detailed explanation about pure function, I encourage you to read this article on Medium.

Mutable and immutable data

This concept goes hand in hand with pure functions. We must guarantee that arguments passed to pure remains with their initial state.

Since JavaScript is not a fully functional programming language, we have structures that are mutable, like arrays and objects. And what I mean by mutable? Well take a look at this code:

var str = "JavaScript IS great";
var loweredCaseStr = str.toLowerCase();
console.log(A- `${str} B - ${loweredCaseStr}`); // A - JavaScript IS great B - javascript is great

Notice that str variable remains with the initial format value. Even after we used it to initialize the loweredCaseStr variable. Well strings in JavaScript, like many other languages, are immutable. Meaning that data cannot be changed once created. So every time, when an immutable type suffers a change, under the hood, JavaScript (using V8 engine as reference) creates a new memory stack allocation with the new value and replaces the old stack memory reference associated. This mechanism allows us to “change” the value of an immutable type of data, but what really happens is that under the hood it is being completely replaced with a new value and memory reference.

Whereas, mutable types like arrays and objects, work completely differently. Because, when mutable data is being changed, we have a new memory space, which is named the heap. Compare to immutable types, mutable types are store in both spaces, the stack, and the heap. Whereas, the immutable are only stored in the stack.

So, when we change a mutable type, under the hood JavaScript, use the stack reference associated to change the value stored in the heap. Therefore, the value is not being replaced, actually is being changed, that’s why we called then mutable because to produce a change, there is no need to replace its value with a new memory allocation.

Let’s take a look at how mutable types works in JavaScript:

var customer = {
name: "John"
}
function addLastNameToCustomer(customer){
customer.lastName = "Carter";
}
addLastNameToCustomer(customer);console.log(customer); //{ name: 'John', lastName: 'Carter' }

Since objects in JavaScript are mutable. When addLastNameToCustomer function takes the customer as an argument, under the hood, JavaScript uses the stack to get the heap’s pointer and change the value, mutating the object on the same memory allocation. This is perfectly fine, but from pure functions scope, this is not allowed because the arguments cannot mutate. So how do we work with mutable types and pure functions in JavaScript?

There are several ways to work with mutable types in JavaScript. If you like to keep solutions with external dependencies at a minimum, you should use the built-in way to do it, using the spread operator or Object.assing() approaches. I won’t write about them in this post. However, if you are interested I encourage you to read this post. It gives a complete explanation about the spread operator, Object.assing() and shallow-copy.

But, if you don’t complain about using external dependencies like me, let me show you Immutable.js and Immer.js. Both are libraries to work with mutable data and came to solve a lot of headaches when working with mutable data in JavaScript.

Immutable.js is created by Facebook, works great, the learning curve is small and it is more popular than immer. However, comparing to Immer.js, Immutable.js, in my opinion, is too verbose. I love Immer.js because its usage is simpler and allows me to work with mutable data more naturally.

So, accordingly to the same example below. This would be the correct way to make our addLastNameToCustomer function fits as a pure function using Immer.js:

import { produce } from "immer"; function addLastNameToCustomer(customer){
return produce(customer, draft =>{
draft.lastName = "Carter";
});
}

The produce function from Immer.js does all the dirty job. The first argument is our source and the second one is a function executed by Immer to apply the required changes. Internally, Immer.js creates a new object and prevents the original object’s mutation. Under the hood, it is a new memory allocation. In most cases, we are going to work with our mutable objects with the approach.

Working with immutable data provides several benefits, we gain a performance boost since immutable data are easier to compare, changes are easier to detect, our data flow is more predictable, and immutable data provides a better environment for concurrency computation.

But, there is a dark side as well. The main issue associated with immutability is memory allocation, as I mentioned before it requires a new stack or stack and heap allocation. Working with large immutable data sets could drive our application to memory overheads and memory leaks. I support the Immutable.js or Immer.js approach, due to, internally they implement a technique named Structural Sharing to reduce room for these dark side repercussions and improve memory usage.

Const is not immutability

In JavaScript, it is important not to confuse const, with immutability. const creates a variable name binding that can’t be reassigned after creation. const does not create immutable objects. You can’t change the object that the binding refers to, but you can still change the properties of the object, which means that bindings created with const are mutable, not immutable.

If you want to create an immutable object you should use Object.freeze() method:

const lang = Object.freeze({
name: 'JavaScript'
});
lang.name = 'Csharp';
// Error: Cannot assign to read only property 'name' of object Object

Object freeze implementation to create immutable data in JS

However, frozen objects are superficially immutable. Because, the top-level primitive properties of a frozen object can’t change, but any property which is also an object (including arrays, etc…) can still be mutated. So even frozen objects are not immutable unless you walk the whole object tree and freeze every object property. For example:

const lang = Object.freeze({
name: 'JavaScript',
versions: Object.freeze({
id: 1
})
});

Shared states

This is the last concept, and I want you to put special interest in this section because I will blend up all previous concepts while explaining this. Shared state is any variable, object, or memory space that exists in a shared scope, or as the property of an object being passed between scopes. A shared scope can include global scope or closure scopes. We must avoid shared states inside pure functions. Let me show you with a simple example, how shared states can be a mess:

const a = {
amount: 3
}
const actionA = () => a.amount += 1;const actionB = () => a.amount *= a.amount;actionA();
actionB();
console.log(a); // 16

Here, actionA and actionB internally have a reference to a.amount, they are changing the property directly (same memory point), this an example of a shared state. The problem with this implementation is that changing the order in which functions are called can cause a cascade of failures because functions that act on a shared state are timing-dependent. Let’s see, the same definition, but now switching the execution order:

const a = {
amount: 3
}
const actionA = () => a.amount += 1;const actionB = () => a.amount *= a.amount;actionB();
actionA();
console.log(a); // 10

Do you see? How a tiny change, a line switch, changed the final result. The order matters, and matters a lot. We should avoid these kinds of implementations, they are fragile and drives our application to be buggy very easily, also working with shared states are the perfect candidates to produce memory leaks. That’s why pure functions cannot have shared states. Let me show you, how to implement the same program using pure functions:

const a = {
amount: 3
};

const actionA = x => Object.assign({}, x, { amount: x.amount + 1});

const actionB = x => Object.assign({}, x, { amount: x.amount * x.amount});

console.log(actionA(actionB(a)).amount); // 10
actionB(a);
console.log(a); // {amount: 3}
actionA(a);
console.log(actionA(actionB(a)).amount); // 10

It is a little bit different and multiple things happened here, let’s explore them step by step:

  1. actionA and actionB are now pure functions, we removed the shared state by preventing argument mutation. We applied the immutability principle, avoid shared state principle and same arguments, same result principle.
  2. We are now able to call functions in a different order and get the same result. Take a look before console.log(a), I called actionB at first. When you avoid a shared state, the timing and order of function calls don’t change the result of calling the function.
  3. We create a well-shaped function composition because all composing functions are pure.
  4. We killed side-effects this makes function calls completely independent of other function calls, which can radically simplify changes and refactoring.

Conclusion

Functional programming is a huge topic, I tried to bring up the most important points when creating code properly using FP in JavaScript. But, if you want to go deeper, I recommend you to read this book. Right now, all want you to remember is that functional programming relies on these concepts and techniques:

  • High order functions.
  • Pure functions.
  • Function composition, over imperative flow control. Remember to currying functions that take more than one argument.
  • Avoid shared state.
  • Avoid mutating state.
  • Avoid side effects.
  • FP is a declarative programming paradigm, it favors what to do, rather than how to do it

It is a hot topic nowadays, libraries like Redux.js and React.js have a strong functional programming orientation. I am a strong supporter of you always should know how things work under the hood. Don’t be stack overflow dependent.

Copy and paste code, is not the right path to become a ninja developer.

With this in mind, I hope that you had learned how FP works and how to apply it with JavaScript.

I hope you enjoyed reading this post. Thanks for reading, and may the odds be in your favor,

--

--

Josue Alpizar

Hi, I’m Josue, a software developer and graphic designer. I’m an adventurer and wanderer. Geek passionate about coding stuff. JavaScript and C#.