I’m working on a small JavaScript project and need to remove specific elements from an array by value or index, but I’m getting confused between methods like splice, filter, and delete. Some attempts are leaving empty slots or not updating the original array as I expect. Can someone explain the right ways to remove items from an array, with examples of when to use each method for best performance and clean results?
You are mixing three different behaviors. Quick breakdown with examples.
- Remove by index, mutate original array
Use splice.
const arr = [10, 20, 30, 40]
// remove 1 element at index 1
arr.splice(1, 1) // returns [20]
console.log(arr) // [10, 30, 40]
// remove 2 elements starting at index 0
arr.splice(0, 2)
Rule: first arg is start index, second arg is how many to remove.
Do NOT use delete for array holes. Example:
const a = [1, 2, 3]
delete a[1]
console.log(a) // [1, empty, 3]
console.log(a.length) // 3
You get a sparse array, length stays the same, loops act weird.
- Remove by value, keep original reference
Find the index, then splice.
const arr = [‘a’, ‘b’, ‘c’, ‘b’]
const idx = arr.indexOf(‘b’)
if (idx !== -1) {
arr.splice(idx, 1)
}
Now arr is [‘a’, ‘c’, ‘b’].
If you want to remove all matches, use a loop.
let idx
while ((idx = arr.indexOf(‘b’)) !== -1) {
arr.splice(idx, 1)
}
- Remove by value, return new array, do not mutate original
Use filter.
const arr = [1, 2, 3, 2]
const withoutTwos = arr.filter(x => x !== 2)
console.log(arr) // [1, 2, 3, 2]
console.log(withoutTwos) // [1, 3]
Good if you use functional style or React state.
- Remove by index, return new array
Use slice and spread.
const arr = [10, 20, 30, 40]
const indexToRemove = 1
const newArr = [
…arr.slice(0, indexToRemove),
…arr.slice(indexToRemove + 1)
]
arr stays the same.
- Summary rules
Use splice when you want to change the original array by index.
Use filter when you want a new array by value.
Avoid delete on arrays. It leaves holes and keeps the length.
You’re not crazy, JavaScript arrays are just petty like that sometimes.
@kakeru already covered the “classic trio” (splice, filter, delete) really well, so I’ll avoid re-explaining those line by line. Instead, here are a few extra angles that usually trip people up and how to avoid the empty-slot / weird-behavior mess.
1. The “delete” trap is worse than it looks
You already saw that delete arr[i] leaves a hole. The subtle extra problem: some array methods actually skip those holes.
const a = [1, 2, 3];
delete a[1];
a.forEach(v => console.log(v)); // logs 1 and 3, but not 2
for (const i in a) console.log(i) // '0', '2'
console.log(a.length); // 3
So your logic might think “index 1 is there but undefined” while array methods treat it like it doesn’t exist. That’s how bugs get born. If you want undefined as a valid value, assign it:
a[1] = undefined; // no hole, length & indices stay sane
Still not “removing” anything, but at least it’s consistent.
2. Remove by value with conditions, not just equality
Instead of:
// remove all 'b'
arr = arr.filter(x => x !== 'b');
You can use any condition:
// keep only active users
const activeUsers = users.filter(u => u.active);
// remove items under some threshold
const cleaned = nums.filter(n => n >= 0);
This is way nicer than hunting indices if your rule is “anything that matches this logic,” not just a single value.
3. Removing the first or last element
If your “index to remove” is specifically at the start or end, you can simplify:
const arr = [1, 2, 3, 4];
arr.shift(); // remove first -> [2, 3, 4]
arr.pop(); // remove last -> [1, 2, 3]
Both mutate the array, similar to splice, but shorter and clearer for those specific positions.
I slightly disagree with pretending splice is always the best for mutation: for “remove first” and “remove last,” shift and pop are just more readable.
4. Removing one item by value without re-scanning
A pattern I like:
function removeFirstMatch(arr, predicate) {
const idx = arr.findIndex(predicate);
if (idx >= 0) arr.splice(idx, 1);
return arr;
}
// remove first user with id = 5
removeFirstMatch(users, u => u.id === 5);
Better than using indexOf when your “value” is actually a property on an object.
5. Immutable update for specific index (like in React)
Instead of:
// remove at index i, keep original
const newArr = arr.filter((_, idx) => idx !== i);
This avoids splice and is easier to read than slicing + spreading for most cases:
const i = 2;
const newArr = arr.filter((_, idx) => idx !== i);
Yes, it loops the whole array, but for most UI / state use cases it’s totally fine and very clear.
6. If you’re seeing “empty slots”, double check:
- That you did not use
delete. - That some library / framework is not doing
arr.length = ...on you. - That your logs aren’t just showing
emptycells from earlier accidental deletes.
Quick sanity check:
console.log(arr, arr.length);
console.log(Object.keys(arr)); // shows actual existing indices
If length is bigger than the highest index in Object.keys, you’ve got holes.
TL;DR:
- Never use
deleteto remove from arrays unless you want a sparse array (you probably don’t). - For mutating:
splice,pop,shift. - For new array:
filter(by value or condition) orfilter-by-index for a single removal. - If you see “empty” in dev tools, you’ve hit sparse territory and should refactor the removal method.
You are basically choosing between three “schools” of array removal:
- Index-based and mutating (
splice,pop,shift) - Value/condition-based and immutable (
filter,slice+ spread) - “Pretend it’s gone” hacks (
delete, settingundefined)
@codecrafter and @kakeru covered the first two really well, so here are some extra patterns and gotchas that usually show up once you move beyond trivial examples.
1. “Remove, but keep order stable and logic clear”
For a single index in an immutable style, I actually prefer this over the slice + spread sandwich:
function removeIndex(arr, index) {
return arr.filter((_, i) => i !== index);
}
Pros:
- Easy to read, especially for beginners or in React state.
- No mental math with
slice(0, i)andslice(i + 1).
Con:
- Iterates the whole array, so for very large arrays in performance-critical code,
splicemay be better.
I slightly disagree with the idea that slice+spread is more “standard”. In many codebases, a tiny helper like removeIndex is clearer and gets reused.
2. “Remove first match by predicate” pattern
For objects where you care about some property:
function removeFirstBy(arr, predicate) {
const idx = arr.findIndex(predicate);
if (idx < 0) return arr;
const copy = arr.slice();
copy.splice(idx, 1);
return copy;
}
// Example: remove first user with id 42
const newUsers = removeFirstBy(users, u => u.id === 42);
This gives you:
- Non-mutating behavior.
- No repeated
indexOfor full array rescans. - Works with complex matching logic, not just equality.
3. “Remove many by predicate, but mutate”
If you actually want to mutate in-place and remove multiple items, a backward loop is safer than repeatedly calling splice in a forward loop:
function removeAllInPlace(arr, predicate) {
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i], i, arr)) {
arr.splice(i, 1);
}
}
return arr;
}
// Example: remove all negatives, mutating the same array
removeAllInPlace(nums, n => n < 0);
Why backward?
- When you splice forward, indices shift and you can accidentally skip elements.
4. When delete is actually acceptable
Both @codecrafter and @kakeru say “avoid delete,” and that is good default advice. I will add a tiny nuance: delete is sometimes fine if you genuinely want a sparse array, for example using an array as a loose sparse map and you do not rely on index iteration.
Example where it is intentional:
const cache = [];
cache[1000] = 'value';
delete cache[1000]; // remove that sparse entry
Cons:
- Methods like
forEach,map,filterskip holes. - Debugging gets confusing when you see
emptyin devtools.
So for normal application data, still avoid it.
5. “Logical delete” instead of structural delete
Sometimes the best option is: do not remove at all, just mark as removed:
item.deleted = true;
// use it:
const visibleItems = items.filter(i => !i.deleted);
This is useful when:
- You want undo/redo.
- You need to keep history or indices stable.
- You do not want React keys or UI positions to reshuffle.
6. Quick mental cheat sheet
- Need to change original array by index:
splice,pop,shift. - Need a new array based on a condition:
filter. - Need a new array minus specific index:
filter((_, i) => i !== index)or slice+spread. - Seeing
emptyin your array inspector: at some point you useddeleteor manually manipulatedlength.
Both @codecrafter and @kakeru gave solid baselines; the patterns above should keep you from running into the “empty slot” / “why did this loop skip my element” headaches again.