Functional programming is a way of creating computer programs by utilizing expressions and functions, while avoiding changes to the original state and data.
Functional programming aims to produce code that is easier to comprehend and less prone to errors by adhering to these principles. This is accomplished by staying away from control flow statements such as (for, while, break, continue, goto), which tend to make code more difficult to follow. Additionally, functional programming encourages the use of pure, predictable functions, which are less likely to contain bugs.
This article will delve into functional programming using JavaScript. We will explore various JavaScript methods and features that make this possible. Finally, we will examine different concepts associated with functional programming and understand why they are so effective.
However, before diving into functional programming, it’s crucial to grasp the distinction between pure and impure functions.
Pure vs. Impure Functions
Pure functions take input and produce a consistent output without causing any side effects in the external environment.
| |
In this example, add is considered a pure function because, for any given value of a and b, the output will always remain the same.
| |
Conversely, getId is not a pure function. This is because it relies on the global variable SECRET to calculate the output. If SECRET were to change, the getId function would produce a different result for the same input, making it impure.
| |
This function is also impure due to two reasons: (1) it utilizes a non-local variable to compute its output, and (2) it generates a side effect by modifying a variable outside its scope.

This could pose challenges if we needed to debug this code. For instance, we might need to determine the current value of id_count, identify other functions modifying id_count, and check for any functions depending on id_count.
These are precisely the reasons why pure functions are exclusively used in functional programming.
Moreover, pure functions offer the advantage of being parallelizable and memoizable, unlike the previous two functions. This characteristic contributes to creating high-performance code.
The Tenets of Functional Programming
As we’ve seen, functional programming relies on certain fundamental rules:
- Avoid data mutation.
- Utilize pure functions: these produce consistent output for given inputs and have no side effects.
- Employ expressions and declarations.
Adhering to these principles ensures that our code can be considered functional.
Functional Programming in JavaScript
JavaScript inherently provides functions that facilitate functional programming. Some examples include String.prototype.slice, Array.protoype.filter, and Array.prototype.join.
On the other hand, Array.prototype.forEach and Array.prototype.push are considered impure functions.
While one might argue that Array.prototype.forEach isn’t inherently impure by design, it’s important to consider that it’s primarily used for mutating non-local data or causing side effects. Therefore, categorizing it as an impure function is reasonable.
Furthermore, JavaScript offers the const declaration, which aligns perfectly with functional programming principles as it prevents data mutation.
Pure Functions in JavaScript
Let’s examine some of the pure functions (methods) provided by JavaScript:
Filter
As its name implies, this function filters an array.
| |
The condition in this case is a function that processes each element of the array and determines whether to retain or discard it, returning a truthy boolean value accordingly.
| |
It’s worth noting that filterEven is a pure function. If it were impure, the entire filter call would also become impure.
Map
The map function applies a given function to each element of an array and generates a new array based on the return values of the function calls.
| |
Here, mapper represents a function that takes an array element as input and returns the processed output.
| |
Reduce
reduce condenses an array into a single value.
| |
reducer is a function that takes the accumulated value and the next array element as input and returns the updated value. This process is repeated for all elements in the array, one after the other.
| |

Concat
concat appends new items to an existing array, resulting in a new array. Unlike push(), which modifies the original array (making it impure), concat preserves data immutability.
| |
The same outcome can be achieved using the spread operator.
| |
Object.assign
Object.assign copies values from a given object to a new object. This method is particularly useful in functional programming for creating new objects based on existing ones while maintaining data immutability.
| |
With the introduction of ES6, this can also be achieved using the spread operator.
| |
Creating Your Own Pure Function
We can also define our custom pure functions. For instance, let’s create one that duplicates a string n number of times.
| |
This function takes a string and replicates it n times, returning the newly generated string.
| |
Higher-order Functions
Higher-order functions are functions that accept a function as an argument or return a function. They are often used to enhance the functionality of existing functions.
| |
In this example, we define a higher-order function called withLog that accepts a function and returns a modified function. The returned function logs a message before executing the original function.
| |
The withLog HOF can be applied to other functions without conflicts or requiring additional code. This seamless integration demonstrates the elegance of higher-order functions.
| |
It’s also possible to invoke it without explicitly defining a combining function.
| |
Currying
Currying involves breaking down a function that takes multiple arguments into a series of nested higher-order functions, each accepting one argument at a time.
Consider the add function:
| |
Currying this function would involve rewriting it to distribute arguments across multiple levels:
| |
A key advantage of currying is memoization. With curried functions, we can memoize specific arguments in a function call, allowing their reuse without redundant computation.
| |
This approach proves more efficient than repeatedly passing both arguments.
| |
Furthermore, we can refactor our curried function to be more concise. Since each level of the curried function call consists of a single-line return statement, we can utilize ES6’s arrow functions to simplify it:
| |
Composition
In mathematics, composition refers to chaining the output of one function as the input of another to create a combined output. This concept translates seamlessly to functional programming due to the use of pure functions.
To illustrate, let’s define a couple of functions. The first one, range, takes a starting number a and an ending number b and generates an array containing numbers from a to b.
| |
Next, we have the multiply function, which takes an array and multiplies all its elements.
| |
Let’s use these functions together to calculate the factorial of a number:
| |
This factorial calculation function mirrors the mathematical composition principle f(x) = g(h(x)).
Concluding Words
This article explored pure and impure functions, functional programming in general, JavaScript features that support it, and some fundamental functional programming concepts.
Hopefully, this exploration has sparked your interest in functional programming and inspired you to experiment with it in your code. Embracing functional programming can be an enriching learning experience and a significant milestone in your software development journey.
Functional programming is a well-researched and robust approach to writing computer programs. With the introduction of ES6, JavaScript now provides a more robust and enjoyable functional programming experience than ever before.