Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. How to remove an element from an array in JavaScript
Learn

How to remove an element from an array in JavaScript

To remove an element from an array in JavaScript, filter() keeps the original and splice() removes in place. Plus the traps that corrupt your data.

By Max Arthur
Co-Founder & Content Marketer·September 6, 2026·11 Min read
White text reading 'How to remove an element from an array in JavaScript' on a dark purple dotted background

To remove an element from an array in JavaScript, use filter() if you want a new array, or indexOf() with splice() if you want to change the original:

// New array, original untouched
const without = langs.filter((l) => l !== "php");

// Original changed in place
const i = langs.indexOf("php");
if (i !== -1) langs.splice(i, 1);

That is the whole answer, and one of those two lines is what you want roughly always.

Which one, though, is a real decision with real consequences, and the rest of this post is about making it correctly. Every output shown below came from running the code, so what you read is what the runtime prints.

The only decision that matters: copy or mutate

Forget memorising six methods. There is one question: do you want the original array changed, or do you want a new array and the original left alone?

Makes a copy Changes the original
Remove by value filter() indexOf() + splice()
Remove by index toSpliced() splice()
Remove the last item slice(0, -1) pop()
Remove the first item slice(1) shift()

Copying is the safer default. Nothing else holding a reference to that array gets surprised, and in React it is not optional: state has to be replaced, not edited, or nothing re-renders.

Mutating is right when the array is genuinely yours and large enough that copying it repeatedly would be wasteful.

filter: remove by value, keep the original

filter() runs a test on every element and keeps the ones that pass. To remove something, keep everything that is not it.

const langs = ["html", "css", "php", "js"];
const without = langs.filter((l) => l !== "php");

console.log(without); // [ 'html', 'css', 'js' ]
console.log(langs); // [ 'html', 'css', 'php', 'js' ]

The original still has four items. That is the point.

filter() also removes every match, not just the first:

const nums = [1, 2, 2, 3, 2, 4];
console.log(nums.filter((n) => n !== 2)); // [ 1, 3, 4 ]

All three twos are gone in one pass. If you only wanted the first one gone, filter() is the wrong tool and splice() is the right one.

indexOf and splice: remove by value, in place

splice() works on positions, not values, so removing a known value takes two steps: find where it is, then cut.

const langs = ["html", "css", "php", "js"];
const i = langs.indexOf("php");
if (i !== -1) langs.splice(i, 1);

console.log(langs); // [ 'html', 'css', 'js' ]

The arguments are splice(startIndex, howMany). Here that reads as "from position 2, remove 1 item".

It also hands back what it took, as an array:

const langs = ["html", "css", "php", "js"];
const removed = langs.splice(2, 1);

console.log(removed); // [ 'php' ]
console.log(langs); // [ 'html', 'css', 'js' ]

Now the traps, and there are three.

Trap 1: the missing if deletes the wrong thing

This looks like a harmless tidy-up:

const langs = ["html", "css", "js"];
langs.splice(langs.indexOf("php"), 1);

console.log(langs); // [ 'html', 'css' ]

There is no "php" in that array. Something got deleted anyway, and it was "js".

indexOf() returns -1 when it finds nothing. splice() treats a negative index as counting back from the end, so -1 means the last element. The two behaviours are individually sensible and together they silently delete real data.

This is why the if (i !== -1) guard is not optional politeness. It is the fix for a bug that never throws an error and never appears in a stack trace.

Trap 2: splice inside a loop skips elements

Every article warns about this. Here is what it actually does.

const nums = [1, 2, 2, 3, 2, 4];

for (let i = 0; i < nums.length; i++) {
  if (nums[i] === 2) nums.splice(i, 1);
}

console.log(nums); // [ 1, 2, 3, 4 ]

One 2 survived.

The reason is that splice() shifts everything after the cut down by one position, while i keeps going up. Remove the item at index 1 and the item that was at index 2 is now at index 1, but the loop has already moved on to index 2. Every element directly following a removed one gets skipped.

Two fixes:

// 1. Use filter, and stop thinking about indexes
const clean = nums.filter((n) => n !== 2); // [ 1, 3, 4 ]

// 2. Or loop backwards, so removals only affect indexes you have passed
for (let i = nums.length - 1; i >= 0; i--) {
  if (nums[i] === 2) nums.splice(i, 1);
}
// [ 1, 3, 4 ]

Both give [ 1, 3, 4 ]. Prefer the first one. The backwards loop is correct but it makes the next reader stop and work out why.

Trap 3: delete leaves a hole

delete is a JavaScript operator, and on an array it does something surprising:

const langs = ["html", "css", "php", "js"];
delete langs[2];

console.log(langs); // [ 'html', 'css', <1 empty item>, 'js' ]
console.log(langs.length); // 4
console.log(langs[2]); // undefined

The value is gone, but the slot is not. Length is still 4, and the array now has an empty slot in the middle that some methods skip and others treat as undefined.

Do not use delete on arrays. It is for removing properties from objects, and an array only tolerates it because arrays are objects underneath.

Removing objects from an array of objects

Most real arrays hold objects, and comparing objects with !== compares identity, not contents. So match on a field instead:

const users = [
  { id: 1, name: "Ada" },
  { id: 2, name: "Linus" },
  { id: 3, name: "Grace" },
];

const left = users.filter((u) => u.id !== 2);
console.log(left);
// [ { id: 1, name: 'Ada' }, { id: 3, name: 'Grace' } ]

To mutate instead, indexOf() cannot help you, because you do not have the object itself. Use findIndex(), which takes a test rather than a value:

const i = users.findIndex((u) => u.id === 2);
console.log(i); // 1
if (i !== -1) users.splice(i, 1);

Same -1 guard, same reason.

Removing by position instead of by value

filter() gets a second argument, the index, so it can do position-based removal without splice():

const langs = ["html", "css", "php", "js"];

console.log(langs.filter((_, i) => i !== 2)); // [ 'html', 'css', 'js' ]

The _ is a convention meaning "I am not using this parameter". Some linters enforce it, all readers understand it.

This is handy for list UIs, where you usually know which row was clicked rather than what it contained. It also sidesteps the duplicate problem: if two items are equal, removing by value removes both, and removing by index removes exactly the one you meant.

Removing several items at once

Do not loop and remove one at a time. Build the set of things to drop, then filter once:

const users = [
  { id: 1, name: "Ada" },
  { id: 2, name: "Linus" },
  { id: 3, name: "Grace" },
  { id: 4, name: "Alan" },
];

const remove = new Set([2, 4]);
const left = users.filter((u) => !remove.has(u.id));

console.log(left);
// [ { id: 1, name: 'Ada' }, { id: 3, name: 'Grace' } ]

The Set is not decoration. Using an array and includes() inside the filter means scanning that array once per item, and the cost grows with both lists multiplied together.

Counted on a 5,000-item list removing 5,000 ids:

Approach Comparisons
removeList.includes(x) inside the filter 15,626,250
removeSet.has(x) inside the filter 5,000

Same output, three thousand times the work. Set.has() is a hash lookup and does not care how big the set is, which is the whole point. Big O Notation Explained covers why that gap widens as the lists grow.

In React, removing means replacing

If you are removing from state, splice() is not just discouraged. It does not work.

const items = [1, 2, 3];
const same = items;
same.splice(0, 1);

console.log(items === same); // true
console.log(items); // [ 2, 3 ]

The array changed and it is still the same array. React decides whether to re-render by comparing the old value to the new one, and items === same is true, so as far as React is concerned nothing happened. Your data is different and your screen is not.

filter() returns a different array:

const items2 = [1, 2, 3];
const next = items2.filter((n) => n !== 1);

console.log(items2 === next); // false
console.log(items2); // [ 1, 2, 3 ]
console.log(next); // [ 2, 3 ]

Different identity, so the re-render happens:

setItems((current) => current.filter((item) => item.id !== id));

The same reasoning applies anywhere something watches an array for changes, which is most state libraries and every framework with reactive rendering.

Frozen arrays make the rule loud rather than silent:

const frozen = Object.freeze(["a", "b"]);

console.log(frozen.filter((x) => x !== "a")); // [ 'b' ]
frozen.splice(0, 1);
// TypeError: Cannot assign to read only property '0' of object '[object Array]'

filter() is fine because it never touches the original. splice() throws. If you want mutation bugs to announce themselves during development, freezing your data is a blunt but effective way to get there.

The rest of the toolkit

Removing from the ends has dedicated methods, and they return the removed item:

const langs = ["html", "css", "php", "js"];

console.log(langs.pop()); // 'js'
console.log(langs.shift()); // 'html'
console.log(langs); // [ 'css', 'php' ]

toSpliced() is the copying version of splice(), same arguments, new array:

const langs = ["html", "css", "php", "js"];

console.log(langs.toSpliced(2, 1)); // [ 'html', 'css', 'js' ]
console.log(langs); // [ 'html', 'css', 'php', 'js' ]

It is the cleanest way to remove by index without mutating. It is also recent, so check the MDN compatibility table before shipping it to old browsers. filter() has no such caveat.

If you are removing things often, an array may be the wrong shape. A Set removes by value in one call and tells you whether anything was there:

const tags = new Set(["html", "css", "php"]);

console.log(tags.delete("php")); // true
console.log(tags.delete("rust")); // false
console.log([...tags]); // [ 'html', 'css' ]

No index lookup, no -1 trap, no duplicates. Spread it back into an array when you need one.

Removing a key from an object is a different job

Worth clearing up, because the same search often means this instead, and the answer inverts.

On an object, delete is correct:

const user = { id: 1, name: "Ada", temp: true };
delete user.temp;

console.log(user); // { id: 1, name: 'Ada' }

No hole, no length to go stale, no leftover slot. Objects have named keys rather than positions, so removing one takes nothing with it.

To do it without mutating, destructure the unwanted key out and keep the rest:

const { temp, ...rest } = user;
console.log(rest); // { id: 1, name: 'Ada' }

temp is now a variable you ignore, and rest is a new object without it. Some linters will complain about the unused variable, and the usual fix is a comment or a rename to _temp.

So the rule reads: delete on objects, filter on arrays. It is the same operator behaving sensibly in one place and badly in the other, and mixing them up is the single most common cause of arrays with mysterious empty slots.

What to use, in one paragraph

Use filter() unless you have a reason not to. It removes by value, removes every match, never mutates, has no compatibility caveats, and cannot delete the wrong element. Use indexOf() plus splice() when you specifically need in-place removal or only want the first match gone, and always guard on -1. Use pop() and shift() for the ends. Never use delete on an array. If you find yourself writing removal code constantly, reach for a Set.

The one exception to "prefer filter()" is a genuinely large array you are editing repeatedly in a tight loop, where allocating a fresh copy each time costs more than the removal does. That situation is rarer than it feels, and it is worth confirming with a measurement rather than assuming. Most arrays in most applications hold tens or hundreds of items, and at that size the copy is free and the clarity is not.

Run these yourself

Reading array methods and using them are different skills, and the gap shows up the first time an off-by-one bug costs you an evening. JavaScript Basics puts arrays in a browser editor with a live console, so you can break splice() on purpose and watch exactly which element disappears.

Next up: JavaScript Closures Explained, because filter() and friends take a function, and understanding what that function can see is the next thing worth knowing. The JavaScript hub has the full order.

Keep reading

More from the blog

White text reading 'How zip() works in Python' on a dark purple dotted background
October 14, 2026·8 min readLearn

zip() in Python: loop over two lists at once

zip pairs items by position and stops at the shortest input, discarding the rest silently. strict=True is the fix.

Read more
White text reading 'How to sort a list in Python' on a dark purple dotted background
October 13, 2026·9 min readLearn

Sorting in Python: sort() vs sorted() vs key=

sort() changes the list and returns None. sorted() returns a new list. The key argument is what makes both useful.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons