To remove an item from a TypeScript array, we call splice(index, 1) when we know its position, or filter() when we want a new array without every matching value. The methods pop() and shift() remove the last or the first element, and when we know only the value, we find its index with indexOf() or findIndex() before calling splice(). ES2023 added toSpliced(), which removes by index but returns a copy.
We remove array items whenever a user deletes something from a list in our app, such as a product from a shopping cart or a finished task from a to-do list.
The following example removes elements in each of these ways, and the comments show the array after every step.
const nums = [1, 2, 3, 4, 5, 3];
// 1. Remove the last element
const last = nums.pop(); // last = 3, nums = [1, 2, 3, 4, 5]
// 2. Remove the first element
const first = nums.shift(); // first = 1, nums = [2, 3, 4, 5]
// 3. Remove by index
const removed = nums.splice(1, 1); // removed = [3], nums = [2, 4, 5]
// 4. Remove by value
const index = nums.indexOf(4);
if (index !== -1) nums.splice(index, 1); // nums = [2, 5]
// 5. New array without some elements
const fruits = ["apple", "banana", "apple", "cherry"];
const noApples = fruits.filter((f) => f !== "apple"); // noApples = ["banana", "cherry"]
const noSecond = fruits.toSpliced(1, 1); // noSecond = ["apple", "apple", "cherry"]
// 6. Empty the array
nums.length = 0; // nums = []
Notice that pop(), shift() and splice() change nums itself, whereas filter() and toSpliced() leave fruits as it was.
TypeScript uses the same JavaScript methods, but it adds two checks that matter when we remove items.
- Methods that may find nothing, such as pop() on an empty array, return T | undefined, so the compiler makes us handle the missing case.
- A readonly array has no removing methods at all.
Next, we go through each method, the -1 bug that removes the wrong element, removing inside a loop, why the delete operator leaves holes, and how to empty an array.
1. Removing the First or Last Element
The method pop() removes the last element and shift() removes the first one, and both return the removed element and shorten the array by one. On an empty array, they return undefined instead of throwing an error.
const names = ["Lokesh", "Raj", "John"];
// 1. pop() removes from the end
const last = names.pop(); // last = "John", typed string | undefined
// 2. shift() removes from the start
const first = names.shift(); // first = "Lokesh"
// 3. Empty array: no error, undefined
const empty: string[] = [];
const nothing = empty.pop(); // nothing = undefined
Because the array could be empty, TypeScript types the result as string | undefined, not string, so in strict mode the compiler reports an error when we use it as a plain string.
const names = ["Lokesh", "Raj"];
const last: string = names.pop();
const upper = names.shift().toUpperCase();
src/err.ts(3,7): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
src/err.ts(4,15): error TS2532: Object is possibly 'undefined'.
In strict mode, we handle the missing value in one of these ways.
- Check the value first with if (last !== undefined).
- Give a default value with names.pop() ?? “nobody”.
- Use optional chaining, as in names.shift()?.toUpperCase().
The two methods also differ in cost, because pop() only shortens the array, whereas shift() moves every remaining element one index to the left. For example, a job queue that takes each job from the front of a very large array with shift() moves all the waiting jobs on every call, so the cost of shift() grows with the queue size.
2. Removing by Index With splice()
The method splice(start, deleteCount) removes deleteCount elements starting at index start and returns them as an array. The elements after the removed ones move left to close the gap, and length goes down. When we leave out deleteCount, splice() removes everything from start to the end.
const nums = [10, 20, 30, 40, 50];
// 1. One element at index 1
const one = nums.splice(1, 1); // one = [20], nums = [10, 30, 40, 50]
// 2. Two elements from index 1
const two = nums.splice(1, 2); // two = [30, 40], nums = [10, 50]
// 3. Everything from an index to the end
const letters = ["a", "b", "c", "d"];
const tail = letters.splice(2); // tail = ["c", "d"], letters = ["a", "b"]
The return value is always an array, even for a single element, so to get the removed value itself, we read the first element with nums.splice(1, 1)[0]. A negative start counts from the end, so splice(-1, 1) removes the last element, which causes the bug in section 3.
3. Removing a Value: indexOf() and findIndex()
To remove an element by its value, we first look up its position. The method indexOf() returns the index of the first element that is equal (===) to the given value, or -1 if there is none. We always check for -1 before calling splice().
const fruits = ["apple", "banana", "cherry"];
const index = fruits.indexOf("banana"); // index = 1
if (index !== -1) {
fruits.splice(index, 1); // fruits = ["apple", "cherry"]
}
Without the check, a missing value deletes the wrong element, because indexOf() returns -1 and splice(-1, 1) removes the last element. The code runs without an error, and the bug shows up later as missing data.
const fruits = ["apple", "banana"];
fruits.splice(fruits.indexOf("kiwi"), 1); // fruits = ["apple"], banana is gone
For an array of objects, indexOf() compares references, so it finds only the exact same object. To remove by a property, we use findIndex() with a condition, and since it also returns -1 when nothing matches, the same check applies.
interface Person { name: string; age: number; }
const people: Person[] = [
{ name: "Lokesh", age: 37 },
{ name: "Raj", age: 35 },
{ name: "John", age: 40 },
];
const i = people.findIndex((p) => p.name === "Raj"); // i = 1
if (i !== -1) {
people.splice(i, 1); // Lokesh and John remain
}
Both approaches remove only the first match. To remove the last match, we search with findLastIndex(), which works like find() and findIndex() but starts at the end of the array.
4. Removing Every Match With filter()
When a value can appear more than once, or when the condition is more complex than one value, filter() is the clearest tool. It keeps the elements for which the callback returns true, so to remove elements, we write a callback that describes the ones we want to keep. The method filter() returns a new array and does not change the original.
const nums = [1, 2, 2, 3, 2];
// 1. Every 2
const noTwos = nums.filter((n) => n !== 2); // noTwos = [1, 3]
// 2. Several values at once
const unwanted = new Set([1, 3]);
const kept = nums.filter((n) => !unwanted.has(n)); // kept = [2, 2, 2]
// 3. null and undefined
const raw = [1, null, 2, undefined];
const clean = raw.filter((n) => n != null); // clean = [1, 2], typed number[]
Step 3 also cleans up the type. Since TypeScript 5.5, the compiler infers that the callback (n) => n != null lets only numbers through, so clean is typed number[] instead of (number | null | undefined)[]. A truthy check such as (n) => n does not get this inference, because it would also drop the number 0.
Because filter() returns a new array, we either assign it to a new variable or reassign the old one (with let), and other variables that point to the old array still see all elements. For example, when a user unsubscribes from several newsletters at once, we filter the subscription list against a Set of the removed names and assign the result back to the list.
5. Removing From a Copy
Removing in place is fine for arrays that one function owns, but for state, function parameters and readonly arrays, we want a new array and an untouched original. For example, a React component must not change its state array, so it removes a to-do item by building a new array and passing it to the state setter. Three non-mutating methods cover every case.
- Use toSpliced() to remove by index.
- Use slice() to remove from either end.
- Use filter() to remove by value.
const nums = [10, 20, 30, 40];
// 1. Without the element at index 1 (ES2023)
const a = nums.toSpliced(1, 1); // a = [10, 30, 40]
// 2. Without the first or last element
const b = nums.slice(1); // b = [20, 30, 40]
const c = nums.slice(0, -1); // c = [10, 20, 30]
// 3. Without a value
const d = nums.filter((n) => n !== 30); // d = [10, 20, 40]
// nums is unchanged: [10, 20, 30, 40]
The method toSpliced() accepts the arguments of splice() and returns the new array, not the removed elements. The compiler knows this method only when lib in tsconfig.json is ES2023 or newer, and Node.js supports it from version 20. The start and end arguments of slice() also accept negative values, as slice(0, -1) shows.
Each in-place method has a copying partner, so switching is a one-line change when an array becomes shared state.
| Removes in place | Returns a new array |
|---|---|
| pop() | slice(0, -1) |
| shift() | slice(1) |
| splice(i, n) | toSpliced(i, n) |
| indexOf() + splice() | filter((x) => x !== value) |
6. Removing Elements Inside a Loop
Calling splice() inside a forward for loop is a classic bug. After a removal, the next element moves into the current index, but the loop counter still increases, so the loop never checks that element. We can see the problem with two equal values next to each other.
const nums = [1, 2, 2, 3];
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 2) nums.splice(i, 1);
}
// nums = [1, 2, 3]: the second 2 was skipped
Looping backwards fixes it, because removing an element moves only the elements after it, and a backward loop has already visited those.
const nums = [1, 2, 2, 3];
for (let i = nums.length - 1; i >= 0; i--) {
if (nums[i] === 2) nums.splice(i, 1);
}
// nums = [1, 3]
In most code, nums.filter((n) => n !== 2) is the better fix, because it is one line and cannot skip elements. We use the backward loop only when we must change the array in place because other code holds a reference to it. Each splice() call moves the elements after it, so for very large arrays with many removals, filter() is also faster.
7. The delete Operator Leaves a Hole
The delete operator removes a property from an object. On an array, it removes the value at an index but does not move the other elements or change length, so we get a sparse array with an empty slot, and TypeScript compiles it without any warning.
const nums = [1, 2, 3];
delete nums[1]; // nums = [1, <empty>, 3]
const size = nums.length; // size = 3
const middle = nums[1]; // middle = undefined, typed number
nums.forEach((n, i) => console.log(i, n)); // 0 1, 2 3
The empty slot causes several problems.
- The length still counts it.
- The methods forEach() and map() skip it, whereas a for loop does not.
- The type still says number while the value is undefined.
We use splice() or filter() instead and keep delete for object properties.
8. Emptying an Array
There are two ways to remove all elements, and they behave differently when another variable points to the same array.
- Setting length to 0 empties the array object itself, so every reference sees the change.
- Assigning a new empty array changes only the one variable.
const nums = [1, 2, 3];
const sameArray = nums;
// 1. Set the length to 0: every reference sees an empty array
nums.length = 0; // nums = [], sameArray = []
// 2. Assign a new array: only this variable changes
let fruits = ["apple", "banana"];
const oldFruits = fruits;
fruits = []; // fruits = [], oldFruits = ["apple", "banana"]
Setting length = 0 works with a const variable, because it changes the array, not the variable, whereas assigning [] needs let. We use length = 0 when other code should see the empty array, and a new array when other code should keep the old values. For example, a chat app that clears the message list on logout sets length = 0, so every component that holds the same array shows an empty list.
9. Example Code
Each snippet is part of a runnable project in the TypeScript-Examples repository. The src/index.ts file runs all sections and prints the array after every removal, including the bugs from section 3 and section 6. The project uses TypeScript 7.0.2 with lib ES2024, so we need Node.js 22 or newer to run it.
npm install
npm start
10. Conclusion
To remove an item from a TypeScript array, we use pop() or shift() for the ends, splice(index, count) for a known position, and indexOf() or findIndex() followed by splice() for a known value, always after checking for -1. The method filter() removes every match and returns a new array, and toSpliced() and slice() give the copying versions of the index-based methods. We avoid splice() in a forward loop, which skips elements, and the delete operator, which leaves a hole in the array.
11. References
These pages describe each method’s behavior and the TypeScript 5.5 change that makes filter() narrow types.
- MDN: Array.prototype.splice()
- MDN: Array.prototype.toSpliced()
- MDN: Array.prototype.indexOf()
- MDN: Sparse arrays
- TypeScript 5.5 release notes: Inferred Type Predicates
Happy Learning !!