Declarative Javascript And Modern Array Methods

Why is .map() better than a for loop? And why were .reverse() and .sort() considered a 'crime' for so long? Let's talk about Purity and Modern Array Methods.

April 23, 20264 min read2 / 3

Once I understood how to build a Higher-Order Function from scratch, I realized that I don't always need to build them. JavaScript gives us a powerful set of built-in tools like map, filter, and reduce. But as I dug deeper, I discovered that these built-in tools aren't just about convenience -- they are about Purity. This same principle of keeping things isolated and predictable applies everywhere: from execution contexts to the functions we write every day.

The Essentials

  1. Declarative Programming: Telling the computer what you want (e.g., "Map this array") rather than how to do it (e.g., "Start at index 0, increment i...").
  2. Immutability: The practice of never changing your original data, but instead creating new versions of it.
  3. Pure Functions: Functions that have no side effects. They take an input and return an output without changing anything in the global scope.
  4. Mutating vs. Non-Mutating: Knowing which methods change your original array (reverse, sort) and which return a new one (map, toReversed).

The "Crime" of Mutation

For years, JavaScript had a strange inconsistency. While map and filter were "pure" (they returned a new array), other core methods like reverse, sort, and splice were "impure"--they mutated the original array in place.

Imagine you have a list of songs on Spotify. You want to show them in reverse order to the user. You call .reverse(), and suddenly, the original database list is reversed for everyone else too. That's a side effect.

JavaScript
const mySongs = ["Song A", "Song B", "Song C"]; mySongs.reverse(); console.log(mySongs); // ["Song C", "Song B", "Song A"] - The original is GONE!

Modern JS: The Great Fix (ES2023)

In 2023, JavaScript finally introduced non-mutating versions of these "criminals." We now have toReversed(), toSorted(), and toSpliced(). These follow the Higher-Order Function pattern perfectly: they create a brand new array inside, do the work, and return it.

Visualizing Mutation vs. Purity

Let's look at how memory handles these two different approaches. Notice how reverse() changes the original box, while toReversed() leaves it untouched.

JavaScript Execution Engine
Thread of Execution
1const original = [1, 2, 3];
2const mutated = original.reverse();
3const source = [10, 20, 30];
4const pure = source.toReversed();

Step 1:We define our first array.

Memory
original[1, 2, 3]
Call Stack
Global
Bottom of Stack

Going Deeper: Flat, FindLast, and GroupBy

JavaScript continues to add high-level Higher-Order Functions that make our code more declarative. Instead of complex nested for-loops, we can use:

  • .flat(): Unpacks sub-arrays into a single flat list.
  • .findLastIndex(): Like findIndex, but searches from right-to-left.
  • .groupBy(): (ES2024) Automatically categorizes data into an object based on a condition.
JavaScript
const inventory = [ { name: "apples", quantity: 5 }, { name: "bananas", quantity: 0 }, { name: "cherries", quantity: 5 } ]; const result = Object.groupBy(inventory, ({ quantity }) => quantity > 0 ? "ok" : "restock" );

Why This Matters for the "Hard Parts"

Understanding the difference between Imperative (how to do it) and Declarative (what to do) code is vital for technical communication. When you use a Higher-Order Function like map or groupBy, you are communicating your intent to other engineers much more clearly than a nested for-loop ever could.

This focus on clear communication brings us to the next pillar of being an engineer: Pair Programming.


Further Reading and Watching

Practice what you just read.

Mutation vs Purity
1 exercise