by Thomas Barrasso

The first shall be last with JavaScript arrays

1*qpeU4geKupip-i6jxgxF9Q
Sea Salps forming natural “arrays”
So the last shall be [0], and the first [length — 1].
– Adapted from Matthew 20:16

I’ll skip the Malthusian Catastrophe and get to it: arrays are one of the simplest and most important data structures. While terminal elements (first and last) are frequently accessed, Javascript provides no convenient property or method for doing so and using indices can be redundant and prone to side effects and off-by-one errors.

A lesser-known, recent JavaScript TC39 Proposal offers solace in the form of two “new” properties: Array.lastItem & Array.lastIndex.

1*76m-CxmcYIqWbOpln0Za8g
Jeff Atwood @codinghorror

Javascript Arrays

In many programming languages including Javascript, arrays are zero-indexed. The terminal elements–first and last– are accessed via the [0] and [length — 1] indices, respectively. We owe this pleasure to a precedent set by C, where an index represents an offset from the head of an array. That makes zero the first index because it is the array head. Also Dijkstra proclaimed “zero as a most natural number.” So let it be written. So let it be done.

I suspect if you averaged access by index you would find that terminal elements are referenced most often. After all, arrays are commonly used to store a sorted collection and doing so places superlative elements (highest, lowest, oldest, newest, etc.) at the ends.

Unlike other scripting languages (say PHP or Elixir), Javascript does not provide convenient access to terminal array elements. Consider a trivial example of swapping the last elements in two arrays:

let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];  
let lastAnimal = animals[animals.length - 1];animals[animals.length - 1] = faces[faces.length - 1];faces[faces.length - 1] = lastAnimal;

The swapping logic requires 2 arrays referenced 8 times in 3 lines! In real-world code, this can quickly become very repetitive and difficult for a human to parse (though it is perfectly readable for a machine).

What’s more, solely using indices, you cannot define an array and get the last element in the same expression. That might not seem important, but consider another example where the function, getLogins(), makes an asynchronous API call and returns a sorted array. Assuming we want the most recent login event at the end of the array:

let lastLogin = async () => {  let logins = await getLogins();  return logins[logins.length - 1];};

Unless the length is fixed and known in advance, we have to assign the array to a local variable to access the last element. One common way to address this in languages like Python and Ruby is to use negative indices. Then [length - 1] can be shortened to [-1], removing the need for local reference.

I find -1 only marginally more readable than length — 1, and while it is possible to approximate negative array indices in Javascript with ES6 Proxy or Array.slice(-1)[0], both come with significant performance implications for what should otherwise constitute simple random access.

1*Tv90DqFJ1xOyUhbtmm4UXw

Underscore & Lodash

One of the most well-known principles in software development is Don’t Repeat Yourself (DRY). Since accessing terminal elements is so common, why not write a helper function to do it? Fortunately, many libraries like Underscore and Lodash already provide utilities for _.first & _.last.

This offers a big improvement in the lastLogin() example above:

let lastLogin = async () => _.last(await getLogins());

But when it comes to the example of swapping last elements, the improvement is less significant:

let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];  
let lastAnimal = _.last(animals);animals[animals.length - 1] = _.last(faces);faces[faces.length - 1] = lastAnimal;

These utility functions removed 2 of the 8 references, only now we introduced an external dependency that, oddly enough, does not include a function for setting terminal elements.

Most likely such a function is deliberately excluded because its API would be confusing and hard to readable. Early versions of Lodash provided a method _.last(array, n) where n was the number of items from the end but it was ultimately removed in favor of _.take(array, n).

Assuming nums is an array of numbers, what would be the expected behavior of _.last(nums, n)? It could return the last two elements like _.take, or it could set the value of the last element equal to n.

If we were to write a function for setting the last element in an array, there are only a few approaches to consider using pure functions, method chaining, or using prototype:

let nums = ['d', 'e', 'v', 'e', 'l']; // set first = last
_.first(faces, _.last(faces));        // Lodash style
$(faces).first($(faces).last());      // jQuery style
faces.first(faces.last());            // prototype

I do not find any of these approaches to be much of an improvement. In fact, something important is lost here. Each performs an assignment, but none use the assignment operator (=).This could be made more apparent with naming conventions like getLast and setFirst, but that quickly becomes overly verbose. Not to mention the fifth circle of hell is full of programmers forced to navigate “self-documenting” legacy code where the only way to access or modify data is through getters and setters.

Somehow, it looks like we are stuck with [0] & [length — 1]

Or are we? ?

The Proposal

As mentioned, an ECMAScript Technical Candidate (TC39) proposal attempts to address this problem by defining two new properties on the Array object: lastItem & lastIndex. This proposal is already supported in core-js 3 and usable today in Babel 7 & TypeScript. Even if you are not using a transpiler, this proposal includes a polyfill.

Personally, I do not find much value in lastIndex and prefer Ruby’s shorter naming for first and last, although this was ruled out because of potential web compatibility issues. I am also surprised that this proposal does not suggest a firstItem property for consistency and symmetry.

In the interim, I can offer a no-dependency, Ruby-esque approach in ES6:

First & Last

We now have two new Array properties–first & last–and a solution that:

✓ Uses the assignment operator

✓ Does not clone the array

✓ Can define an array and get a terminal element in one expression

✓ Is human-readable

✓ Provides one interface for getting & setting

We can rewrite lastLogin() again in a single line:

let lastLogin = async () => (await getLogins()).last;

But the real win comes when we swap the last elements in two arrays with half the number of references:

let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];  
let lastAnimal = animals.last;animals.last = faces.last;faces.last = lastAnimal;

Everything is perfect and we have solved one of CS’ most difficult problems. There are no evil covenants hiding in this approach…

Prototype Paranoia

Surely there is no one [programmer] on earth so righteous as to do good without ever sinning.– Adapted from Ecclesiastes 7:20

Many consider extending a native Object’s prototype an anti-pattern and a crime punishable by 100 years of programming in Java. Prior to the introduction of the enumerable property, extending Object.prototype could change the behavior of for in loops. It could also lead to conflict between various libraries, frameworks, and third-party dependencies.

Perhaps the most insidious issue is that, without compile-time tools, a simple spelling mistake could inadvertently create an associative array.

let faces = ["?", "?", "?", "?", "?"];let ln = faces.length  
faces.lst = "?"; // (5) ["?", "?", "?", "?", "?", lst: "?"]  
faces.lst("?");  // Uncaught TypeError: faces.lst is not a function 
faces[ln] = "?"; // (6) ["?", "?", "?", "?", "?", "?"]  

This concern is not unique to our approach, it applies to all native Object prototypes (including arrays). Yet this offers safety in a different form. Arrays in Javascript are not fixed in length and consequently, there are no IndexOutOfBoundsExceptions. Using Array.last ensures we do not accidentally try to access [length] and unintentionally enter undefined territory.

No matter which approach you take, there are pitfalls. Once again, software proves to be an art of making tradeoffs.

Continuing with the extraneous biblical reference, assuming we do not believe extending Array.prototype is an eternal sin, or we’re willing to take a bite of the forbidden fruit, we can use this concise and readable syntax today!

Last Words

Programs must be written for people to read, and only incidentally for machines to execute. – Harold Abelson

In scripting languages like Javascript, I prefer code that is functional, concise, and readable. When it comes to accessing terminal array elements, I find the Array.last property to be the most elegant. In a production front-end application, I might favor Lodash to minimize conflict and cross-browser concerns. But in Node back-end services where I control the environment, I prefer these custom properties.

I am certainly not the first, nor will I be the last, to appreciate the value (or caution about the implications) of properties like Array.lastItem, which is hopefully coming soon to a version of ECMAScript near you.

1*Z63EqxzV68XKUa3b81d8wg

Follow me on LinkedIn · GitHub · Medium