Declarative vs. Imperative Programming

Abstract image used to represent 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 can be seen both within vanilla JavaScript and in libraries such as 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.


Declarative Patterns in ES6 and Later Editions

Both examples are vanilla JavaScript. Array.prototype.map was standardised in ES5, in 2009; the arrow function and const declarations used here arrived in ES2015. The distinction is how much of the iteration we describe ourselves, not whether we use a framework.

ES2015 made several patterns more concise, and later editions continued that work. These features can support a declarative style, but their presence alone does not make a program declarative.

Here are a few examples, with the later additions separated from ES2015:

  • 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:

    Iterable spread, including arrays, arrived in ES2015. Object rest and spread followed in ES2018. These make some copying and combining operations more concise.
  • Promises and

    async/await: Promises were standardised in ES2015; async functions followed in ES2017. They give us different ways to organise asynchronous work, but an async function can still describe a sequence of imperative steps.

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

React encourages us to describe the UI for a given state, and I often find that easier to follow than a sequence of manual DOM changes. That is useful in the right setting, rather than a guarantee of simpler or faster code. Imperative steps remain useful too, including inside declarative abstractions.

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.

For the UI work I do, declarative patterns often reduce the amount of lowlevel coordination I have to write. I find that helpful, but difficulty and productivity depend on the problem and the abstractions available. We still need to be precise about the behaviour we want.

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.

Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.