Remove an element from an Array in Javascript

From time to time, one comes to the issue of removing elements from an Array. While there are certainly ways to do this with built-in functions like splice(), they just don't quite do everything I want. So, as usual, I made my own.

The first iteration I created was designed to be able to polymorphically take either a single argument (the index of the element to remove), or two arguments defining a range. It then basically concatenates the part of the array before the removed element(s) with the part of the array after the removed element(s). Here's how that took shape:

const removeElement = (arr, from, to) => {
  const rest = arr.slice(to || from);
  arr.length = from;
  return [...rest];
}

This works out pretty well, and I even started using it in a project. However, I soon came across the case where I wanted to remove the last element in an array. Sure, that's just as easy as removeElement(arr, arr.length - 1), right? Well, sure, but I'm too lazy for all that typing. Ideally what I really want is negative indexing like Python allows, where arr[-1] === arr[arr.length - 1].

I did a little researching around to see how others might have solved the same issue, and came across this blog post by none other than John Resig, the creator of jQuery itself and the author of one of my favorite books on Javascript.

In the post, John outlined five goals for his Array.remove:

  • It had to add an extra method to an array object that would allow me to remove an item by index (e.g. array.remove(1) to remove the second item).
  • It had to be able to remove items by negative index (e.g. array.remove(-1) to remove the last item in the array).
  • It had to be able to remove a group of items by index, and negative index (e.g. array.remove(0,2) to remove the first three items and array.remove(-2,-1) to remove the last two items).
  • It had to be destructive (modifying the original array).
  • It had to behave like other destructive array methods (returning the new array length - like how push and unshift work).

Those sound like pretty good goals to me. It covers the base cases my function already solves and adds in the negative indexing I'd like, plus it raises the issue of making the function destructive. Even better, John implemented his function as an addition to the Array object itself, which fits object oriented-ness quite well.

So I combined my function and ideas with John's function, and came up with something I'm quite happy with. This function is already being used in several projects and is ready to be utilized elsewhere:

The complete function

Array.prototype.removeElement

// Array#removeElement - By Jade M Thornton (ISC Licensed)
Array.prototype.removeElement = function(from, to) {
  const rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  this.push(...rest);
  return this;
}

Example usage

let a = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// remove the element at index 1 (second element)
a.removeElement(1);
// a --> [1, 3, 4, 5, 6, 7, 8, 9]

// remove the last element (a.length - 1)
a.removeElement(-1);
// a --> [1, 3, 4, 5, 6, 7, 8]

// remove elements 2-4 (INCLUSIVE)
a.removeElement(2, 4);
// a --> [1, 3, 7, 8];

// remove elements (-1)-(-2) (INCLUSIVE)
a.removeElement(-1, -2);
// a --> [1, 3]

Explanation

What's that you ask? What in David Hilbert's beard are those six lines of code doing? Let's take a look at it again, along with some helpful line numbers.

Array.prototype.removeElement = function(from, to) {
  const rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  this.push(...rest);
  return this;
}

Each line is doing something important, so let's break them up and talk through what's going down.

In line 1 is the assignment of our new anonymous function to Array.prototype.removeElement. Unlike my first implementation above, this allows us to use the function as a method on an instance of an Array itself. In a more heavily object-oriented language like Java, this would be similar to adding our function to the Array object, which we would later instantiate.

Line 2, in a nutshell, grabs the part of the Array starting immediately after the last element we want to remove. For example, if the function call is arr.removeElement(2); then rest will be the elements of the Array starting at index 3 and continuing to the end of the Array. However, there's some trickiness in there for handling different cases. Let's break this line down further.

  • What we're assigning to rest is a slice of this, which is referring to the Array we're working on.
  • The argument (yes, singular argument) takes advantage of falsy values. The first part, (to || from), evaluates to the value of to if it's assigned to a value. If to is undefined, which is falsy, the statement short-circuits to the value of from.
  • We then add 1 to the value of (to || from), which gets us the element immediately after the removal section.
  • In the case that (to || from) is -1, meaning we want to remove the last element, then adding 1 brings it to 0. we don't want to roll over to the first element like that, so we take advantage of 0 being falsy. (to || from) + 1 || this.length lets us translate -1 into the last element.

Line 3 handles the front part of the Array, the part before the removal area. We use a conditional operator to handle negative indexing. If from is negative, this.length gets from added to itself (which is the same as subtracting the negative). Otherwise, we just use from directly. Because Javascript is (rightfully) zero-indexed, this results in this Array being shortened correctly.

In line 4, we reassemble the section before the removed element(s), stored in this, with the section after the removed element(s), stored in rest. We do this by pushing rest onto this Array—utilizing the spread operator—which achieves the goal of destroying the old Array in favor of the new, altered one.

The last (meaningful) line, line 5, the new Array is returned so the function can be used in an assignment. Technically there's a fifth line, but it's a closing brace. I'm going to let you guess what that does.




All code created by me, Jade Michael Thornton, is licensed under the terms of the ISC License, just like the rest of this site.