Write Delightful Declarative JavaScript
Use functional programming style to write readable code
I recently have been going down a rabbit hole of functional programming after dabbling with it for a couple of years. Functional programming has a ton of advantages. This includes functional purity (i.e., same results every time), which makes your functions easier to test. Hands down, though, the benefit I love most from functional programming is the readability gained from composing small functions together. This style is known as declarative programming.
In this article, I wanted to explore the concept of declarative programming broadly and what techniques and concepts can allow a developer to start introducing it into their codebase. While the article is grounded in some functional programming concepts and techniques, the focus is not on being militant about enforcing functional programming conventions. In my opinion, readable code begets functional code!
My hope is that by the end of this article, you should have a sense of what declarative programming is, learn some concepts to help you begin writing more declaratively today, and learn a bit about Ramda, a popular functional programming library.
Before we begin, let’s dive into what is declarative programming and how it differs from the more common imperative programming style.
I Do Declare: Declarative vs. Imperative Programming
The difference between these two programming styles is usually defined as follows.

But what does this mean in practice? Let’s look at a quick real-life scenario. You are making lunch for a friend you are entertaining. They want a ham and cheese sandwich.
Let’s see how they would interact with us in an imperative vs. a declarative style.

While a ridiculous example, it is indicative of the differences between the two approaches. In programming, you may be familiar with a common syntax that is declarative in nature, SQL. SQL lets us define what we want to retrieve, not the steps needed to retrieve it.
Let’s take a hypothetical SQL query and compare it to a hypothetical imperative implementation of it.

The query above identifies the top ten sales reps in the U.S. by volume. As you can see, the declarative syntax of the SQL
query communicates the same information in a much more succinct way.
For someone new who is reading our code, they get a good sense of what we are trying to accomplish with our query in one line. Using an imperative approach, they must take each step and piece together a mental model to understand what the code is trying to achieve. This is the power of declarative programming.
In JavaScript, you may be familiar with array methods like filter
, map
, and reduce
that take the place of more imperative for loop
implementations. If you want to see an example, this quick sixty-second video has an example.
Before we continue, let’s take a quick look at Ramda.
So What Is Ramda?
Ramda is one of the most well-known functional programming libraries in JavaScript. Originally a fork of Lodash, this library has a ton of useful functions that allow us to write more readable and elegant code. The library also has useful features like auto-currying, immutability, and data-last functions. Taken together, these give developers a ton of flexibility when writing code. While many of the concepts we explore in this tutorial can be leveraged in plain JavaScript, Ramda may give you an easier interface to accomplish these tasks.
Of note, we will look at its pipe
and compose
functions in this tutorial. This is an implementation of a functional programming concept that we could implement in plain JavaScript, but we won't. We will instead use Ramda’s implementation in our example to learn. I definitely recommend that people spend time looking at Ramda. Here is an example of the terse code that you can write by leveraging and combining the library’s many functions and some of the concepts we will touch upon.
The code above does the following:
- It evaluates an array of API responses to see if any response returned an error.
- If an error is found, we return a custom error object with a predefined message of “One or more API calls failed.”
- If no error is found, we return our original array of API responses to use in our next step.
As you can see, Ramda can help us write cleaner, more descriptive code. It is definitely worth exploring.
Now that we have covered Ramda at a high level, let’s talk about some concepts that allow us to build more declarative code.
The Building Blocks of Declarative Code
Let’s now look at some concepts to help us write more declarative code. We can utilize a lot of these concepts in plain JavaScript, but using Ramda may give us some added benefits. As a result, I will note where Ramda may give us some added advantage. Let’s begin.
JavaScript functions are first-class citizens
JavaScript functions being first-class citizens means that functions are treated like any other type in JavaScript. Just like a string, you can:
- Assign a function’s definition to a variable
- Pass in a function as an argument into another function
- Return a function definition as the result of a function call
This allows us to not only write higher-order functions. We can use this to give us flexibility in combining and extending functions. This enables us to also compose functions together and use a point-free programming style.
Write small functions and extract logic checks
This is one of the easier ways to make your code more readable. Instead of larger imperative blocks, look to extract each operation and condition-check into separate named variables. This is a good beginner's step towards function composition. Here is an example.
Note: We can further improve this code through ternaries, but just by extracting logic to variables, we already gain a great deal of readability.
Avoid returning null and undefined whenever possible!
Null exceptions are a super common bug found in software. The inventor of the null type even apologized for it, estimating that it may have caused companies up to a billion dollars in pain over the years. Avoid returning these values since they can lead to unexpected errors in your code, and consider what other alternative data type makes sense for your function to return.
I recommend instead to return a value of the same type, like an empty array or empty string. It is a good practice to test to see if your function can handle null
and undefined
gracefully. Libraries like Lodash and Ramda have many functions that can help you avoid returning null
orundefined
accidentally when accessing a variable as well.
Point-free programming
Point-free or tacit programming style is a style where the arguments of a function are omitted when defining or passing the function. This is because a higher-level abstraction consistently invokes a passed function in a standard manner. This makes defining the arguments redundant. Let’s look at an example of point-free definition vs. other implementations.
As you can see, this leads to more readable code and is easier for the developer, too. No longer must we agonize about naming our argument to properly convey the data we are operating on! In plain JavaScript, you can start using this pattern in higher-level abstractions like map
, filter
, reduce
, then
, and catch
.
We should note that using a functional library like Ramda allows us to significantly start introducing more point-free code into our application since many of its functions work like these higher abstractions. Take a look again at our Ramda example above.
Currying and partial application
Currying is the practice of transforming a function that takes many arguments to one that only takes one argument. We can accomplish this by partially applying one passed argument at a time and returning a function for the remaining arguments. This can be used to help reuse and extend our code. It also helps us build abstractions that make our code easier to read and use. Here is an example of using currying to build abstractions.
While this may seem like a lot of work upfront, you achieve a ton of gains in readability and reusability. In the end, we are left with a declarative function that only does one very specific thing and only needs one passed argument to work. This allows other devs on your team to easily use your existing functions or extend functionality by partially applying new arguments.
Ramda has auto-currying by default, but it also allows you to pass the arguments in whatever order you like. For example, if a function takes three arguments, you can pass one to three arguments. Ramda also contains a curry function that converts any plain JavaScript function to a curried version of itself.
Piping and composing
Note: What follows is a simplistic explanation. There are some mathematical principles underpinning functional programming. If you’re interested, I recommend checking out this free book.
At its most basic, piping and its sibling compose
are used in functional programming to chain functions together end to end. The result of one function is passed as an argument to the next function in the chain. These functions are defined in a point-free style. Piping and composing are not built into JavaScript, though there is a stage-one proposal at the moment for a pipe operator. We can use pipe
or compose
by either building it ourselves or by using Ramda. Below is an example of how pipe
and compose
work using the Ramda variant:
As you can see, the pipe
and compose
functions are quite powerful. I personally gravitate towards pipe
since I find it easier to follow. Using pipe
and by composing smaller functions, we can replace a lot of our imperative code bloat, like if-else
blocks and array iterators.
Last Thoughts
Readable code begets functional code!
Hopefully, you have left with a better sense of some concepts you can explore to build more declarative code. I definitely recommend taking a look at the Ramda library and playing with the concepts we went over in this tutorial. Additionally, this can be a gateway to functional programming and its many benefits.
Lastly, thank you for reading. I am thinking of doing a follow-up exploring how to use Ramda to effectively compose functions. If interested, please reach out and let me know!