Understanding Imperative vs. Declarative Programming in JavaScript
Photo by Markus Spiske on Unsplash
In the world of software engineering, understanding different programming paradigms is crucial for writing efficient and maintainable code. One of the most fundamental paradigm decisions to make is the choice between using imperative versus declarative programming. While both approaches can achieve the same outcomes, they do so in fundamentally different ways. We’ll explore the differences between these two patterns, with a focus on JavaScript, and provide examples to illustrate the pros and cons of each.
Imperative vs. Declarative: A Real World Example
Understanding the benefits and drawbacks of these programming concepts is best achieved through hands-on experience. Imagine the challenges of debugging complex, imperatively written code, or the frustration of navigating over-engineered, overly abstract solutions. While these experiences can be invaluable, a real-world analogy can provide a helpful starting point for understanding the differences between imperative and declarative programming.
The first analogy I heard to describe the imperative/declarative relationship was using an automobile’s air conditioner control panel, so let’s explore that in depth.
Imperative Design of an Automobile A/C Control Panel
1970 Chevrolet A/C Control Panel | Source: ChevyHardcore.com
Description: In the past, some cars’ A/C controls used imperative design, where the user set a fan level, selected a vent, and decided a heating/cooling level.
Usage: Using this system isn’t too complicated, but it requires an understanding of how all three settings work and how different combinations of settings translate to getting the car to the desired temperature. Not only that, but if the weather or conditions change, the user needs to adjust the settings according. If the weather gets colder or hotter, or if the driver takes a weekend trip out of town, those settings might not be appropriate. Heating the car while on a trip to the desert or cooling the car in the cold mornings don’t make much sense.
In other words: The user tells the car how to cool or heat the vehicle.
Declarative Design of an Automobile A/C Control Panel
A Tesla Model 3 Control Panel | Source: Motor Trend
Description: Modern cars, like the Tesla Model 3, have more declarative temperature controls. While the user may dive in and adjust the A/C settings more granularly, the main control is a just a left arrow, a right arrow, and the temperature display.
Usage: By providing controls to adjust the temperature, the user gets to focus on the desired output rather than the underlying mechanics. The user sets the cabin temperature and the car’s sensors and controllers will adjust the underlying settings to maintain the desired output. The user only needs to be concerned about concepts they understand, like comfortable temperatures, and can allow the car to be concerned with vents, fans, and HVAC.
In other words: The user tells the car what temperature they want.
What is Imperative Programming?
As discussed above in the analogy, imperative programming is a paradigm that focuses on how to achieve a particular task. It involves explicitly giving the runtime a sequence of commands or steps to follow in order to reach the desired outcome. In imperative programming, you often define the control flow and manage the state of your program as you go.
What is Declarative Programming?
Declarative programming, on the other hand, focuses on what you want to achieve rather than the intricate details of how to achieve it. In this paradigm, you describe the desired outcome, and the underlying system takes care of the rest. The control flow and state management — the parts of the algorithm that can be made generic and reused elsewhere—are often abstracted away from the algorithm at hand.
An Example of Imperative Programming in JavaScript
Let’s look at a classic example, one that many beginning programmers learn in their early software journeys. Say we want to sum all the numbers in an array. An imperative approach in JavaScript would look like the following:
An example of imperative code written in JavaScript
In the example above, we explicitly define every step of the algorithm:
Initialize a
sum
variableLoop through each number in the array
Add each number to the
sum
An Example of Declarative Programming in JavaScript
Let’s look at a declarative approach to achieve the same desired output. Using the same example of summing numbers in an array, a declarative approach in JavaScript might use the reduce
method on the array prototype:
An example of declarative code written in JavaScript
Here, instead of describing the steps to achieve the sum, we declare that we want to reduce the array to a single value by summing its elements. The reduce method abstracts away the loop and state management.
Pros of Imperative Programming
Control: You have precise control over the flow of your program, which can be useful in complex scenarios where you need to manage the state closely.
Performance: Imperative code can sometimes be optimized for performance, as you can fine-tune every step of the process.
Cons of Imperative Programming
Verbosity: Because you must define each step, imperative code can very quickly become verbose, more difficult to read, and even more difficult to code review. These difficulties compound as complexity increases.
Error-Prone: Managing the state manually can lead to bugs, particularly in complicated programs or when operating in lower-level programming languages.
Testing Difficulty: Writing unit tests can sometimes become, by definition, impossible because concerns are intermixed.
Pros of Declarative Programming
Simplicity: Declarative code is often more concise, easier to understand, and easier to read, as it abstracts away the underlying mechanics.
Maintainability: Because you’re focusing on what you want to achieve, the code is typically easier to maintain and less prone to bugs.
Reviewability: Declarative code can be more readable, especially for those who are familiar with the abstractions used. This can make it much easier for code reviews.
Testability: Writing declarative code often involves separating concerns. This allows for individual features to be isolated and tested easily.
Cons of Declarative Programming
Less Control: You have less control over how the task is executed, which can be a drawback if you need fine-tuned performance optimizations.
Over-Abstraction: The abstractions provided by declarative methods might introduce overhead or may not be suitable for all scenarios, particularly when performance is critical.
Conclusion
Choosing between imperative and declarative programming is not a simple choice and often involves consideration. Imperative code can give you a lot of control but often at the expense of simplicity, abstracted code that easy to read and maintain. When you need precise control or performance is an primary focus, an imperative approach might make sense. Most other times it probably makes the most sense to use a declarative approach that can lead to cleaner code. With the advent of bundlers, minification, and other optimization tools, small performance improvements become less impactful. As developers, it’s beneficial to be versatile and comfortable with both paradigms, using each where it best fits the problem at hand.
Subscribe to my newsletter
Read articles from Michael Stromberg directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by