One of JavaScript's most controversial, yet quckly discussed subjects that goes beyond implicit type coercion (e.g., [] + {}) is handling arrays natively, specifically for programmers of other languages learning with the goal of actually understanding the language. While it's one of the most popular programming languages in the world, its design choices around array manipulation have frustrated developers for decades, including newcomers. This is especially clear when trying to do something simple, such as removing an item from an array.
Let's get into the different approaches!
Consider a simple basket of fruit:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
Now, imagine wanting to remove 'orange' from this basket. JavaScript offers several ways to do this, each with its own quirks, return values, and gotchas.
Approach 1: Using the Splice Function
The most common way to remove an element is using splice():
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
const index = fruits.indexOf('orange'); // returns 2
if (index > -1)
{
const removed = fruits.splice(index, 1);
console.log(removed); // returns an array ['orange']
console.log(fruits); // ['apple', 'banana', 'grape', 'mango']
}
And here's where things get interesting. The splice() function doesn't just remove the element, it also mutates the original array AND returns an array of the removed elements. Not the element itself, but an array containing it. This is where the second parameter (set to 1 in this example) comes into play: if you increase it (e.g., to 2 or 3), the array elements will also be returned, such as 'grape' and/or 'mango'.
This return value makes sense when removing multiple items:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
const removed = fruits.splice(1, 3); // remove 3 items starting at index 1
console.log(removed); // ['banana', 'orange', 'grape']
console.log(fruits); // ['apple', 'mango']
But for a single element, getting back a one-item array feels unnecessarily wrapped.
The splice() vs. slice() Naming Confusion
Perhaps the most infamous naming confusion in JavaScript, particularly within arrays, is between splice() and slice(). They differ by just one letter, however do completely opposite things. Let's take a look:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
// slice sounds destructive but it isn't
const sliced = fruits.slice(1, 3);
console.log(sliced); // new array: ['banana', 'orange']
console.log(fruits); // unchanged array: ['apple', 'banana', 'orange', 'grape', 'mango']
// splice sounds like joining but does is destructive
const spliced = fruits.splice(1, 3);
console.log(spliced); // new array: ['banana', 'orange', 'grape']
console.log(fruits); // changed array: ['apple', 'mango']
This mockery is painful. "Slice" sounds like cutting or removing, yet it leaves the original array intact, whereas "Splice" sounds like joining things together (like splicing rope or film). Yet, it actually removes elements and mutates the original array. Also, note that the returned arrays are different when both functions are called!
This single-letter difference has caused countless bugs and hours of debugging frustration. It's considered one of the worst naming decisions in JavaScript's history.
Removing Multiple Elements
When removing multiple elements with the same value using splice(), the approach changes significantly because splice() only removes elements at specific index positions and not by value. As an example, we now have orange twice:
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape', 'orange'];
const index = fruits.indexOf('orange');
// indexOf() only finds the FIRST occurrence
fruits.splice(index, 1); // ['apple', 'banana', 'orange', 'grape', 'orange']
console.log(fruits); // only removed the first 'orange'
To remove multiple occurrences, the code needs to loop backwards through the array:
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape', 'orange'];
// loop backwards to avoid index shifting issues
for (let i = fruits.length - 1; i >= 0; i--)
{
if (fruits[i] === 'orange')
{
fruits.splice(i, 1);
}
}
console.log(fruits); // ['apple', 'banana', 'grape']
Unfortunately, its readability is confusing, so the question arises:
Why loop backwards?
When removing elements while looping forward, the indices shift, and some elements get skipped. Let's see how this would look forward (which is wrong):
const fruits = ['orange', 'orange', 'banana'];
for (let i = 0; i < fruits.length; i++) // normal forward iteration
{
if (fruits[i] === 'orange')
{
fruits.splice(i, 1);
// after removing index 0, the second 'orange' moves to index 0
// but i becomes 1, so it gets skipped
}
}
console.log(fruits); // still has an orange: ['orange', 'banana']
This complexity is one reason filter() is often the preferred native solution for removing multiple values, though it operates differently (more on that later).
Method 2: The Delete Operator
This one is a trap, and isn't so clear at first glance! Some developers try using the delete operator:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
const index = fruits.indexOf('orange');
delete fruits[index];
console.log(fruits); // ['apple', 'banana', empty, 'grape', 'mango']
console.log(fruits.length); // still contains 5 elements
console.log(fruits[2]); // undefined
As noticed (or not), the delete operator doesn't actually remove the element; it sets that position to undefined, leaving a hole in the array. The length remains the same, but iteration becomes problematic, resulting in what's known as a "sparse array."
Using this method with arrays is best avoided. It lingers from JavaScript's object-oriented roots and often surprises developers with its unexpected behavior.
Method 3: The Filter Function
This alternative, easily promising and for a completely different approach, the filter() creates a new array without the unwanted element:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
const filtered = fruits.filter(fruit => (fruit !== 'orange'));
// arrow function keeps it simple (low logic)
console.log(filtered); // ['apple', 'banana', 'grape', 'mango']
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'mango']
As noticed, fruits is unchanged!
This method follows an entirely different design. Instead of mutating the original array, it returns a brand new one. This immutable approach is safer and more predictable, but it comes with a performance cost for large arrays.
Another key difference is that filter() removes multiple occurrences of 'orange', not just the first one as previously seen with splice().
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape'];
const filtered = fruits.filter(fruit => (fruit !== 'orange'));
console.log(filtered); // ['apple', 'banana', 'grape']
And if one is wondering, how about a mutation desugb of this straightforward filter function? JavaScript doesn't have a built-in mutable version of filter(). If mutation of the original array is needed, then here are some options,
Mutable Option 1: Reassign after filtering
let fruits = ['apple', 'orange', 'banana', 'orange', 'grape'];
fruits = fruits.filter(fruit => (fruit !== 'orange'));
console.log(fruits); // ['apple', 'banana', 'grape']
This reassigns the variable but technically creates a new array rather than mutating the original, yet it's readable and straightforward.
Mutable Option 2: Backwards loop with splice()
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape'];
for (let i = fruits.length - 1; i >= 0; i--)
{
if (fruits[i] === 'orange')
{
fruits.splice(i, 1);
}
}
console.log(fruits); // ['apple', 'banana', 'grape']
This is truly mutable, as it actually mutates the original array in place. But as previously pointed out, backwards iteration isn't readable at first glance.
Mutable Option 3: Clear and refill
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape'];
const filtered = fruits.filter(fruit => (fruit !== 'orange'));
fruits.length = 0; // clear the array
fruits.push(...filtered); // refill it
console.log(fruits); // ['apple', 'banana', 'grape']
This is mutable but clunky; its readability is fair, but it requires additional external steps and is unnecessary unless memory becomes the primary constraint.
Additional Methods: The Pop & Shift Functions
Although these additional methods do not remove a specific element, they do, however, remove elements from the end or beginning of an array, which should be recognized:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
const lastFruit = fruits.pop();
console.log(lastFruit); // returns the removed element 'mango'
console.log(fruits); // ['apple', 'banana', 'orange', 'grape']
const firstFruit = fruits.shift();
console.log(firstFruit); // returns the removed element 'apple'
console.log(fruits); // ['banana', 'orange', 'grape']
Notice the inconsistency? Unlike splice(), these methods return the actual element, not an array containing it. Both methods mutate the original array, but their return values follow a different pattern.
The naming is also strange. While pop() makes intuitive sense (like popping something off a stack), shift() is less clear. The term "shift" suggests moving elements around, not removing the first one. Many developers wish it had been called popFront() or removeFirst() instead, as seen in languages like C++ and Swift.
And, so, as a final conclusion:
The filter function always returns a new array, leaving the original unchanged. JavaScript doesn’t have a native method that filters in place while mutating the original array, which is why the backwards loop with the splice function is the go-to solution when true mutation is required.
- In most cases, the filter() is the best choice.
- It’s clear, predictable, removes all matching elements in one pass, and avoids the pitfalls of array mutation.
- The immutable approach also prevents bugs in larger applications where unexpected mutations can cause problems.
- The backwards loop with splice() is the right tool when working with very large arrays where performance matters.
- Or when the original array reference must be preserved (for example, when other parts of the code hold references to it).
- And delete, never use it for arrays.
#HappyCoding
Top comments (0)