Declarative vs Imperative | Software.Land

Declarative vs Imperative

Overview

In the context of programming, declarative and imperative are words used to describe opposite ends of a programming paradigm. But, what is a programming paradigm?

A programming paradigm is a way of writing code that affects how the program runs. This is in contrast to code style (also known as code formatting), which pertains to the visual arrangement of the code, but does not affect the program’s behavior. While both programming paradigms and code style influence the physical layout or structure of the code, the impact of a programming paradigm is functional and fundamental to the program’s behavior. Code style is primarily concerned with readability and aesthetics. For example, if we want to find the sum of a list of integers in Python, we can do either:

integers = [i for i in range(10000000)]

result = sum(integers)

Or, we could do:

integers = [i for i in range(10000000)]

result = 0

for i in integers:
    result += i

Both accomplish the same thing. The flow of execution appears to be identical on the surface. However, where and how the execution occurs is different. The first example is declarative and the summation code executes entirely in the built-in sum() function written in the lower-level language that Python was originally written in: C. The second example is imperative and the summation code executes in both C and Python. The first example executes in an average of 0.23 seconds over 10 runs on a Mac M1 Pro (Python 3.11.3) while the second one takes an average of 0.58 seconds.

This commonplace performance advantage of going declarative over imperative as in the above examples typically only exists in interpreted languages that are themselves written in a lower-level compiled language, such as Python or PHP (these are both written in C). Conversely, if the total work being done in the built-in method is many orders of magnitude greater than an imperative solution, then the imperative solution would be faster. Such an example would likely require the imperative solution to use a different data structure and/or traversal method to outperform the much faster code being executed in the underlying more-performant language (Python vs C). This large performance difference would likely not be reproducible in Java where both approaches would be 100% compiled prior to running:

import java.util.*;
import java.util.stream.Collectors;

public class DeclarativeVsImperative {

    public static void test() {
        // Create a large list of integers for testing
        List<Integer> largeList = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            largeList.add(i);
        }

        // Benchmark the imperative approach
        long startTimeImperative = System.nanoTime();
        List<Integer> resultsImperative = new ArrayList<>();
        for (Integer num : largeList) {
            if (num % 2 != 0) {
                resultsImperative.add(num);
            }
        }
        long endTimeImperative = System.nanoTime();
        long durationImperative = (endTimeImperative - startTimeImperative) / 1000000; // Convert to milliseconds

        // Benchmark the declarative approach
        long startTimeDeclarative = System.nanoTime();
        List<Integer> resultsDeclarative = largeList.stream()
                .filter(num -> num % 2 != 0)
                .collect(Collectors.toList());
        long endTimeDeclarative = System.nanoTime();
        long durationDeclarative = (endTimeDeclarative - startTimeDeclarative) / 1000000; // Convert to milliseconds

        // Output the results
        System.out.println("Imperative approach time: " + durationImperative + " ms");
        System.out.println("Declarative approach time: " + durationDeclarative + " ms");
    }
}

Note: Above code provided by ChatGPT from OpenAI.

Running the above code 100 times results in the declarative approach averaging 65.92ms and the imperative approach averaging 51.44ms (JDK 20.0.1). This cursory benchmark suggests that Java’s declarative methods actually come with a price of performance.

Diving Deeper

As demonstrated by the above example, declarative programming is a layer of abstraction over imperative programming. Generally speaking, the nature of programming tends to become more imperative as you go lower down the layers of abstraction, all the way down to assembly and machine code. Learn more at High Level vs Low Level.

Any language, regardless of its core paradigm, can become more declarative by building abstractions (such as functions that wrap imperative logic).

The Future

Software becomes more declarative over time. As it evolves, more layers of abstraction are built on top of existing layers via new programming constructs and entirely new languages and tools. This lowers the barrier to entry (but does not need to lower the bar to entry) — something that is necessary as automation and Artificial Intelligence displace existing non-creative jobs.

Note: How could the barrier to entry be lowered without lowering the bar to entry? By solving information asymmetry.

Conclusion

  • Declarative code tells the compiler or interpreter what you want to happen.
  • Imperative code tells the compiler or interpreter how you want it to happen.
  • If you’re working in an interpreted language such as Python, use the built-in methods because it’s a win-win of performance and maintainability.
  • If you’re working in a compiled language such as Java and performance is critical, try writing imperatively. Benchmark it to be sure (as you should anyway if performance is critical).

Updated: 2023-12-18


Author

Sam Malayek

Sam Malayek works in Vancouver, using this space to fill in a few gaps. Opinions are his own.