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 it!
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 sever…
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 it!
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 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');
if (index > -1) {
const removed = fruits.splice(index, 1);
console.log(removed); // ['orange'] (returns an ARRAY)
console.log(fruits); // ['apple', 'banana', 'grape', 'mango']
}
Here’s where things get interesting. The splice() method doesn’t just remove the element; it mutates the original array AND returns an array of the removed elements. Not the element itself, but an array containing it.
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 notorious naming confusion in JavaScript is between splice() and slice(). They differ by just one letter but do completely opposite things:
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
// slice() (sounds destructive, but ISN'T)
const sliced = fruits.slice(1, 3);
console.log(sliced); // ['banana', 'orange'] (new array)
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'mango'] (unchanged)
// splice() (sounds like joining, but IS destructive)
const spliced = fruits.splice(1, 3);
console.log(spliced); // ['banana', 'orange', 'grape'] (removed items)
console.log(fruits); // ['apple', 'mango'] (mutated!)
The irony is painful. "Slice" sounds like cutting or removing, yet it leaves the original array intact. "Splice" sounds like joining things together (like splicing rope or film), yet it’s the one that actually removes elements and mutates the array.
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 with the Same Value
When removing multiple elements with the same value using splice(), the approach changes significantly because splice() only removes elements at specific index positions, not by value:
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape', 'orange'];
// Problem: indexOf() only finds the FIRST occurrence
const index = fruits.indexOf('orange');
fruits.splice(index, 1);
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'orange']
// Only removed the first 'orange'!
To remove all 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']
Why loop backwards? When removing elements while looping forward, the indices shift and some elements get skipped:
// WRONG WAY (looping forward)
const fruits = ['orange', 'orange', 'banana'];
for (let i = 0; i < fruits.length; i++) {
if (fruits[i] === 'orange') {
fruits.splice(i, 1);
// After removing index 0, the second 'orange' moves to index 0
// But i becomes 1, so we skip it!
}
}
console.log(fruits); // ['orange', 'banana'] (missed one!)
This complexity is one reason why filter() is often preferred for removing multiple values, though it works differently as we’ll see later.
Method 2: The Pop Function & Shift Function Confusion
For removing from the ends of an array, JavaScript provides pop() and shift():
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
const lastFruit = fruits.pop();
console.log(lastFruit); // 'mango' (returns the ELEMENT itself)
console.log(fruits); // ['apple', 'banana', 'orange', 'grape']
const firstFruit = fruits.shift();
console.log(firstFruit); // 'apple' (returns the ELEMENT itself)
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 peculiar. 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.
Method 3: The Delete Operator
This is one 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 5!
console.log(fruits[2]); // undefined
This is a trap. The delete operator doesn’t actually remove the element; it just sets that position to undefined, leaving a hole in the array. The length remains the same, iteration becomes problematic, creating 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 4: 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');
console.log(filtered); // ['apple', 'banana', 'grape', 'mango']
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'mango'] (unchanged!)
This method follows a completely different philosophy. 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: filter() removes all occurrences of ‘orange’, not just the first one:
const fruits = ['apple', 'orange', 'banana', 'orange', 'grape'];
const filtered = fruits.filter(fruit => fruit !== 'orange');
console.log(filtered); // ['apple', 'banana', 'grape'] (both oranges gone)
What About a Mutable Version of filter()?
JavaScript doesn’t have a built-in mutable version of filter(). If mutation of the original array is needed, here are the options:
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.
Option 2: Use the backwards loop with splice() (truly mutable)
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 actually mutates the original array in place.
Option 3: Clear and refill (mutable but clunky)
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']
The key takeaway: filter() always returns a new array and leaves 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 splice() is the go-to solution when true mutation is required.
Which Method Should Be Used?
For most cases, 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. However, 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), the backwards loop with splice() is the right tool. Never use delete for arrays. As for pop() and shift(), they’re perfectly fine for their specific use case of removing from array ends, just remember they return the element itself, not an array.