Imperative vs. Declarative: What's the best way to loop in JavaScript?
In development, we spend a lot of time manipulating lists of data. To do this, there are two main philosophies: the imperative approach and the declarative approach. Understanding the difference and knowing when to use one or the other can radically improve the quality and readability of your code.
Imperative style: you give the orders
The imperative approach involves telling the computer how to perform a task, step by step. This is the most "classic" method, and the one we often learn first.
Think of for
or while
loops. You initialize a counter, define a stop condition and describe precisely how to move from one element to the next and what to do with it.
**Example: Double every number in an array.
Javascript
const numbers = [1, 2, 3, 4];
const doubled = []; // We need an intermediate array
for (let i = 0; i < numbers.length; i++) {
const number = numbers[i];
doubled.push(number * 2);
}
// doubled is now [2, 4, 6, 8]
- Advantages**: Total control, often unbeatable raw performance.
- Disadvantages**: Verbose, intention (the "what") is drowned out by logic (the "how").
-----
### Declarative style: You express your need
With the declarative approach, you no longer describe the how, but simply **what you want to achieve** as a result. You delegate execution to functions designed for this purpose.
Array methods such as `map`, `filter` and `reduce` are perfect examples of this approach.
**Example: Double every number in an array with `map`.
Javascript
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(number => number * 2);
// doubled is now [2, 4, 6, 8]
The code is more concise and the intention is clear: "I want a new array where every number in the original has been multiplied by two."
map()
: Transforms each element of an array and returns a new array of the same size.filter()
: Selects elements according to a condition and returns a new array with only those elements.reduce()
: Applies a function to all elements to reduce them to a single value (a number, a string, an object...).
Advantages :
- Lisibility: Code expresses intent directly.
- Predictability**: These functions return new arrays without modifying the original (immutability), thus avoiding bugs linked to unexpected modifications (side effects).
- Comprehensibility**: It's easier to chain operations (map().filter()...).
Disadvantages: A very slight loss of performance due to the creation of intermediate functions and arrays.
The special case of forEach
: a false good idea?
forEach
resembles a declarative method, but its use is closer to the imperative. Its purpose is not to transform data, but to execute an action (an edge effect) for each element.
The main criticism of forEach
in modern development is that it encourages the modification of external variables rather than the creation of new data. This can make code more difficult to follow and debug.
As this ÆFLASH article points out, if your goal is to create a new value, map
, filter
or reduce
are almost always better choices. If you absolutely need an edge effect, a for...of
loop is often more explicit about this intention.
The golden rule: Readability first, optimization if necessary ✅
Both approaches are valid, but here's a simple and effective guideline:
- By default, use the declarative approach (
map
,filter
,reduce
). Your code will be cleaner, easier to read and maintain for you and your team. - If, and only if, you identify a bottleneck in a portion of code (after measuring performance!), consider rewriting it with an imperative loop (
for
,while
) to gain speed.
Don't sacrifice clarity for a hypothetical performance gain.
To go further: utility libraries 🚀
Libraries such as Lodash or Ramda take this declarative logic a step further. They offer dozens of utility functions optimized to manipulate data in a clear, concise and functional way. They are an excellent option for projects requiring a lot of data transformation.