TypeScript Remove Item from Array: splice, filter, pop

Remove items from a TypeScript array by position or by value, in place or on a copy, with the common bugs (-1 index, splice in a loop, delete) shown and fixed.

angular-typescript

The right way to remove an item from a TypeScript array depends on what we know about the item. If we know its position, pop(), shift() and splice() remove it from the array in place. If we know its value, we find the index with indexOf() or findIndex() first, or we use filter() to build a new array without every matching element. ES2023 added toSpliced(), which removes by index but returns a copy.

TypeScript runs the same JavaScript methods, but it adds two checks that matter here:

  • 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.

This guide walks 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. The output in every comment comes from a real run on Node.js 22 after compiling with TypeScript 7.0 in strict mode.

For a quick answer, one snippet removes elements in every way this guide covers. The comments track the array after each 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 = []

1. Removing the First or Last Element

pop() removes the last element and shift() removes the first one. Both return the removed element and shorten the array by one. On an empty array, they return undefined and do not throw.

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. Using it as a plain string is a compile error in strict mode:

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'.

We handle the missing value in one of three ways:

  • a check: if (last !== undefined)
  • a default: names.pop() ?? “nobody”
  • optional chaining: names.shift()?.toUpperCase()

The two methods also differ in cost: pop() only shortens the array, while shift() moves every remaining element one index to the left. For a queue that removes from the front of a very large array many times, that difference adds up.

2. Removing by Index With splice()

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. To get the removed value itself, we read the first element: nums.splice(1, 1)[0]. A negative start counts from the end, so splice(-1, 1) removes the last element. That rule is the cause of the bug in the next section.

3. Removing a Value: indexOf() and findIndex()

To remove an element by its value, we first look up its position. 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. 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 only finds the exact same object. To remove by a property, we use findIndex() with a condition. It also returns -1 when nothing matches, so 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 methods remove only the first match. The article on find() and findIndex() covers searching in more detail, including findLastIndex() for the last match.

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, the callback describes the ones we want to keep. 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 only lets numbers through, so clean is typed number[] instead of (number | null | undefined)[]. A truthy check such as (n) => n does not get this treatment, 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). Other variables that point to the old array still see all elements.

5. Removing From a Copy

Removing in place is fine for arrays that one function owns. For state, function parameters and readonly arrays, we want a new array and an untouched original. Three non-mutating methods cover every case:

  • toSpliced() for an index
  • slice() for the ends
  • filter() for values
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]

toSpliced() accepts the arguments of splice() and gives back the new array, not the removed elements. The compiler only knows it when lib in tsconfig.json is ES2023 or newer, and Node.js supports it from version 20. The slice() article explains its start and end arguments in detail.

Each in-place method has a copying partner, which makes it simple to switch when an array becomes shared state:

Removes in placeReturns 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 that element is never checked. Two equal values next to each other show the problem.

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. Removing an element only moves 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: it is one line and cannot skip elements. The backward loop is useful when the array must be changed 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. The result is 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 three problems:

  • The length still counts it.
  • forEach() and map() skip it, while 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"]

length = 0 works with a const variable, because it changes the array, not the variable. 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.

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 two bugs from sections 3 and 6. The project uses TypeScript 7.0.2 with lib ES2024 and needs Node.js 22 or newer.

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. filter() removes every match and returns a new array, and toSpliced() and slice() give the copying versions of the index-based methods. Two things are worth avoiding: splice() in a forward loop, which skips elements, and delete, 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.

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.