Declarative vs. Imperative Programming

Hero image for Declarative vs. Imperative Programming. Image by Andrew Neel.
Hero image for 'Declarative vs. Imperative Programming.' Image by Andrew Neel.

In more modern web development, the more 'maths' or 'computer science' aspects have gone a little by the wayside, where the 'how' of what we're developing has been replaced with writing more declarative, expressive 'what' paradigms. Understanding the difference between what is considered 'declarative programming' versus 'imperative programming' is important. In JavaScript development, in particular, the differences can cater to different problemsolving mindsets, and are most easily highlighted in the development of vanilla JavaScript versus a framework like React.


Understanding Imperative Programming

So, let's start with good, oldfashioned, imperative programming. In imperative programming, we focus on the 'how'. It focuses on prescribing stepbystep instructions for the computer to follow in order to achieve a desired outcome. Consider it a little like following a detailed recipe you must follow to cook a specific dish.

In imperative programming, the development is focused on the control of flow, iterating through data structures and stating explicitly how each operation should be performed.

An Example in JavaScript

const numbers = [1, 2, 3, 4, 5];const doubled = [];for (let i = 0; i < numbers.length; i++) {  doubled.push(numbers[i] * 2);}

So, I don't know about you, but that format of using a for loop with an iterator feels very oldfashioned. This is imperative programming: we are explicitly describing, stepbystep, how to double each number in an array. We initialise an empty array, iterate over each element of the original array, multiply it by 2, and then add it to the new array.


Understanding Declarative Programming

On the other hand, declarative programming focuses more on the 'what'. In declarative programming, we describe the desired outcome without explicitly describing each step needed to achieve that outcome. This paradigm emphasises the expected outcome (the end result), whilst allowing the underlying system or framework to determine the 'how'.

SQL queries, HTML, and functional programming can all be considered as examples of declarative programming.

An Example in JavaScript

const numbers = [1, 2, 3, 4, 5];const doubled = numbers.map((number) => number * 2);

This achieves the same thing as the imperative example above: it takes an array of numbers and creates a new one by multiplying each element by 2. Here, though, we simply declare what we want: a new array of doubled numbers. What we don't do is specify how to loop through the array or how to push elements into the new one. The map() method abstracts those details away, allowing us to focus on the outcome.


The Shift Towards Declarative Programming with ES6

What I'm hoping is obvious from the two previous examples is that in the first (imperative) example, we use oldfashioned, vanilla JavaScript, whereas the declarative example uses map() a feature of ECMAScript 2015 (ES6).

With the release of ES6, we saw a fairly significant shift towards embracing declarative programming. ES6 introduced several features that now streamline how we as developers write code with it. This isn't just a matter of syntax sugar but a fundamental change in the way developers approach problemsolving with JavaScript, these features have made it easier and more intuitive to adopt a declarative approach.

Here are a few quick examples of how ES6 encourages declarative programming:

  • Arrow Functions

    : These provide a more concise syntax for writing functions, making functional programming patterns (common in declarative programming) easier to both read and implement.
  • Template Literals

    : Allow for cleaner and more readable string concatenation, supporting a more declarative way of creating strings.
  • Destructuring Assignment

    : allows us to unpack values from arrays, or properties from objects, into distinct variables all within a single, readable line, promoting a more declarative approach to handling data.
  • Default Parameters

    : Allowing functions to have default values simplifies function calls, supporting a more straightforward, declarative style of coding.
  • Array and Object Spread:

    Simplifies the process of copying or merging arrays and objects, a common operation in both frontend development and in a declarative programming style.
  • Promises and Async/Await

    : Makes asynchronous code easier to read and understand, moving away from the nested callback hell of the past, and towards a more declarative approach to handling asynchronous operations.

This is only a whistlestop tour, but it is fair to say that these features (amongst many others), allow us as developers to write more concise and readable code, focusing on what should be done rather than how it should be done.


Implications in Front‑End Web Development

So, it's fair to say that we're seeing a switch from imperative to declarative programming within frontend web development (and really web development in general). Modern JavaScript frameworks and libraries especially React have gone out of their way to embrace declarative programming, which makes them easier to work with, more efficient, and more readable.

Development in React

By its very nature, React is a declarative JavaScript library. For a long time, it said so very proudly right on its homepage:

A screenshot of the reactjs.org homepage (before their move to react.dev), where 'Declarative' is the first heading on the left of the page.

What this means is that it allows developers to describe what the UI should look like for a given state. React then takes care of rendering the UI accordingly. You could argue this makes React code easier to read and debug.

Imperative vs. Declarative in React

Imperative Approach (Hypothetical)

const button = document.createElement('button');button.innerText = 'Click me';button.addEventListener('click', () => alert('Clicked'));document.body.appendChild(button);

Declarative Approach (React)

const Button = () => {  return <button onClick={() => alert('Clicked')}>Click me</button>;};

So, here we have two examples of how we might create a button with a click event in JavaScript. In the hypothetical imperative example, we have to manually create a button, set its properties, and then add it to the DOM.

In the declarative React example, we can simply describe the button's desired state, and React handles the DOM manipulation for us.


Wrapping up

If frontend (and more general web) development carries on the course it is currently plotting, then really understanding the difference between declarative and imperative programming becomes a bit of a moot point. As I said at the start of this article, the more 'maths' or 'computer science' aspects of development are gradually being abstracted away, wrapped up in a more declarative paradigm. As technology continues to evolve, the balance and blend of these paradigms will also continue to shape the development landscape.

This is good news for the industry in general: imperative programming is much more difficult, which means that developers can produce more code of a better quality more quickly and at a more junior level. We don't need to be as explicit about our requirements or needs from the language we write in.

I do also fear, however, that we might be seeing a diminishing need for deep problemsolving skills and logical thinking. There is a chance that this trend could lead to a generation of developers who are heavily reliant on tools without fully understanding the principles that underlie them. Perhaps this could affect the longerterm ability to tackle complex challenges creatively.

Of course, really I am just showing my age by saying that. I started coding in BASIC on a Commodore VIC20, writing instructions to the compiler linebyline: the very epitome of imperative development...

The original Commodore VIC-20 rainbow logo.

Categories:

  1. Development
  2. JavaScript