Functional Programming, Simplified
A visual introduction to functional programming
What’s Functional Programming?
Functional programming is a pipeline of pure functions.
What does that mean?
To understand this definition, we will use a real-life example. Let’s assume that we have a shipping warehouse and a large store containing a variety of products (books, clothes, certain canned food products, household appliances, etc.).
How do things work inside our warehouse?

Our workflow is a chain of small processing steps arranged so that the output of each element is the input of the next one: a pipeline. Each step exists only once.
The unit of work of a functional code is the function. Each function is unique (single responsibility and DRY — Don’t Repeat Yourself).
Functional programming uses pipelines as its unique method of building.

In math, a function is a short formula that connects outputs to inputs. The same input always gives the same output. In a short formula, variations and behaviors are resumed in a single short relation.
Functional programming is declarative (what is more important than how).
Let’s see these two ways to double the items of an array:
double1
is an imperative code that details, step by step, how to double each element of the array.double2
is a declarative and concise code that reduces the structure of a function down to the most important pieces (a formula).
Did the products change during the warehouse workflow?
No! Going from one stage to another, we add new accessories without mutating or changing the original product.
For example, in the Assorting step, we group ordered products inside a package and then proceed to the Weighing step. When the final user receives the order and unwraps the package, they will be able to get products without mutations or damages.
This is how functional programming works: composing functions to transform inputs without mutating them.

A function never mutates its input variables but creates new, enriched outputs. A function that doesn’t mutate its inputs is a pure function.
Recap
- The unit of work of a functional code is the function.
- Functional programming is a pipeline of pure functions.
- Functional programming is declarative (what > how).
What is the consequence of this?
Isolate and replace
Each stage in our warehouse is independent of the others and does not know of their existence (before or after). Similarly, each function depends only on its inputs. There is no shared state or outside dependency.
If anything goes wrong, we can easily detect the source and isolate the defective component to quickly resume.

If we can easily isolate a function, we can easily unit-test it!
Each function acts as an independent and isolated microservice.
Functions are like mathematical functions:
- A function depends only on its inputs.
- A function doesn’t produce any side effects. For instance, f(x) doesn’t change the input x but produces a new value.
Expandable and easy to rearrange
What if we want to add a new step called Automatic Supervision between Weighing and Loading to check that everything is included inside the pack (quality improvement)?

To be effectively operational, the steps’ inputs and outputs must be compatible:
- The outputs of Weighing should be compatible with the inputs of Automatic Supervision.
- The outputs of Automatic Supervision should be compatible with the inputs of Loading.
That’s all! We don’t need to know how each step works internally (weak coupling)!
If we want to add a function to keep only items greater than four between the double
and sort
functions:

Functions inputs and outputs must be compatible in type and arity:
- Type — The type returned by one function must match the argument type of a receiving function.
- Arity — A receiving function must declare at least one parameter in order to handle the value returned from a preceding function call.
Connecting functions must be compatible in terms of arity and type.
We can also easily rearrange things when their type and arity are compatible.

Predictable and deterministic
In a functional programming world:
- There is no shared state.
- Functions are pure (immutability).
- Each function acts as an independent and isolated microservice (free of side effects).

If the instability factors are filtered out, we can easily predict the outputs at any time.
We can create a time machine that allows us to move forward or backward in the life of the program:

Predictability, determinism, and isolation are the code qualities needed to have good unit tests!
Reducing complexity
Functional programming is the composition of short, single-responsibility, and pure functions.
What does it mean? Let’s look at an example.
How can we calculate (2x+3)²?
- The first step is to calculate (2x+3).
- Then square the output.
f(x) = (2x+3)g(x) = x²h(x) = (g º f)(x) = g(f(x)) = (2x+3)²
A complex mathematical function is the composition of small simple functions!

const finalOutput
= double º sort º display
= display(sort(double(array)))
- Calculate
double(array)
. - Then pipe the result to sort.
- Then pipe the result to display.
The key is composing small steps instead of having a function that does everything.
What do we gain from this? It’s easy to isolate, debug, and test!
There are many patterns of composition. The most important are higher-order functions and chaining.
Let’s dive in!
Higher-Order Functions
“Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.” — Eloquent JavaScript
What does that mean?

transform
is a higher-order function that takes f
as an argument.
Why? Let’s look at how transform
can be written:
transform
is pure: We return a copy instead of mutating the original array.
const data = [1, 4, 2, 8];const double = x => x * 2;
const plusOne = x => x + 1;console.log('double array : ', transform(data, double));
console.log('plus one array : ', transform(data, plusOne));
transform
contains the repeated code to iterate over an array and apply the function to its items.
It’s a way to reuse code!
Instead of repeating the same code, we create a generic higher-order function (abstraction).
Do we find transform
implemented natively in a programming language or is it just a mathematical concept?
Let’s see.
Higher-order functions in JavaScript
In the JavaScript world, we have these available ways:
map
to transform an array:

const sequence = [1, 2, 3, 4, 5];
const doubleSequence = sequence.map((item) => item * 2); // [2,4,6,8,10]
map
:
- Returns a new array without affecting the original array (pure and without side effects).
- Input array length = output array length.

2. filter
to filter an array:

f
is the predicate function.
const isString = value => typeof value === 'string';const values = [12, 'Hi', 1, 'Sun', 'Sky', 8];const valuesMatched = values.filter(isString);console.log('valuesMatched : ', valuesMatched);
// "valuesMatched : ", ["Hi", "Sun", "Sky"]
filter
:
- Returns a new array without affecting the original array (pure and without side effects).
- The output array length depends on how many elements match the predicate (max = input array length).

3. every
and some
.
every
tests whether all the elements of an array satisfy a condition :
[12, 5, 8, 130, 44].every(elem => elem >= 10); // false
[12, 54, 18, 130, 44].every(elem => elem >= 10); // true
[{a:1, b:2}, {a:1, b:3}].every(elem => elem.a === 1); // true
[{a:2, b:2}, {a:1, b:3}].every(elem => elem.a === 1); // false
some
tests if at least one element of the array passes a condition:
[2, 5, 8, 1, 4].some(elem => elem > 10); // false
[12, 5, 8, 1, 4].some(elem => elem > 10); // true (12)
How to Chain Functions
Chaining functions gives you the possibility of assembling small behaviors to obtain a complex result:

const complexArray = [6, 2, 4, 8]
.map(x => x * 2)
.map(x => x + 1)
.filter(x => x > 10);console.log('complexArray : ', complexArray);
// "complexArray : ", [13, 17]
Connecting functions must be compatible in terms of arity and type.
Safety in Functional Programming
Functors
Let’s look at the code below:
const increment = v => v + 1increment('5') // '51'increment({ v: 5 }) // "[object Object]1"
What’s wrong? {v: 5}
is an object.
How can we protect our function?
Let’s now add a function that calculates the double of a number:
const double = (v) => {
if (typeof v !== 'number') {
return NaN
}
return v * 2
}
What’s wrong? There is duplicate code between increment
and double
.
Is there a better way? Let’s see:
Magic! Let’s understand what’s happenening:
- For a given input, we check if it’s a number.
- If it is, then
NumberBox
can apply the function(fn)
and pipe the result to the next function. - Otherwise, the function isn’t applied and
Nan
is piped to the next function. - The context is saved between
applyFunction
(pipe). - The same check continues until the last map, where
NumberBox
returns the last saved value in the context.

Instead of passing the value to the functions, we passed the functions to the value.
We can make a small change to our NumberBox
:
We change applyFunction
with map
:
Yes, this is our map
and this is how we safely chain between functions!
Then what’s a functor?
In essence, a functor is nothing more than a data structure that you can map func- tions over with the purpose of lifting values into a wrapper, modifying them, and then putting them back into a wrapper. It’s a design pattern that defines semantics for how fmap should work. — Luis Atencio, Functional Programming in JS
Perfect!
Their practical purpose is to create a context or an abstraction that allows you to securely manipulate and apply operations to values without changing any original values. This is evident in the way map transforms one array into another without altering the original array; this concept equally translates to any container type. — Luis Atencio, Functional Programming in JS
This is what we want! Safe chaining!
Are functors sufficient?
No. Why not?
When something goes wrong, functors safely continue the execution until the last map
and return the last saved value in the context:
const safeResult = NumberBox({
v: 5})
.map(v => v * 2) // -> executed
.map(v => v + 1) // -> executed
.value // NaN
Functors safely continue the execution (map
) even if an error occurred.
We will solve this problem by using an advanced version of a functor called a monad.
Let’s see!
The Maybe monad
How to write a Maybe
:
A magical example:
A Maybe
monad is a functor because we also map
the function.
However, when a Maybe
encounters an issue, it doesn’t continue. It skips and goes to the default statement: getOrElse
.

Yes, it’s magic!
A Maybe
has two flows: one for success and one for failure. By comparison, the functor only has one flow:

How is this helpful?
A concrete example of use would be processing a response received from a backend. We don’t know if the data received is defined or if it respects the expected type and format.
Therefore, we can wrap the data with Maybe
and chain the transformation functions. If one step fails, all steps fail.
MayBe(Backend.Call()).map().map().getOrElse();
That’s all for this article about functional programming!
Conclusion
In this article, we discussed what functional programming is, its benefits, and its available patterns.
Functional programming is the composition of short, single-responsibility, and pure functions. Pure means that function will never mutate its inputs. The function will never create or cause a side effect.
Functions are more like mathematical functions: declarative and concise (formula).
What do we gain from this?
- We can easily isolate functions and thus easily unit-test them.
- Since each function acts as an independent and isolated microservice, we can easily expand and rearrange the global behavior. How? By using techniques like composition, higher-order functions, and chaining.
Finally, we saw some safety tips on functional programming:
- Functors help us to safely chain functions that are applied on a value. However, they don’t handle errors and exceptions.
- The
Maybe
monad enriches functors with the capability of handling errors.