How to Remove Elements from a List in C#
List<T> in C# has five removal methods, and the right one depends on what you know about the element. Remove(item) deletes the first element equal to a value. RemoveAt(index) deletes the element at a position. RemoveAll(predicate) deletes every element that matches a condition. RemoveRange(index, count) deletes a block, and Clear() empties the list.
All five belong to List<T> in System.Collections.Generic. LINQ has no Remove or RemoveAll method, because LINQ never changes its source. The LINQ way is to build a new list with Where, which is covered below.
Remove by value with Remove
Remove scans from the start and deletes the first element equal to its argument. It returns true when it removed something and false when nothing matched.
var fruits = new List<string> { "apple", "banana", "cherry", "banana" }; bool removed = fruits.Remove("banana"); // true; list is apple, cherry, banana bool missing = fruits.Remove("grape"); // false; list unchanged
Only the first match is deleted. To delete every copy of a value, use RemoveAll.
Remove by index with RemoveAt
RemoveAt deletes the element at a zero-based position and moves every later element one slot to the left. An index outside the list throws ArgumentOutOfRangeException.
var numbers = new List<int> { 10, 20, 30, 40 }; numbers.RemoveAt(1); // removes 20; list is 10, 30, 40 numbers.RemoveAt(numbers.Count - 1); // removes the last element, 40
RemoveRange(index, count) deletes a block in one call. numbers.RemoveRange(0, 2) deletes the first two elements.
Remove where a condition is true with RemoveAll
RemoveAll takes a predicate, which is a function that returns true for each element to delete. It removes all of them in a single pass and returns how many it removed. This is the C# answer to "remove where".
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 }; int count = numbers.RemoveAll(n => n % 2 == 0); // count is 3; list is 1, 3, 5
The same call removes objects by a property, which is the usual way to remove an object from a list.
record User(string Name, bool IsActive); var users = new List<User> { new("ana", true), new("bo", false) }; users.RemoveAll(u => !u.IsActive); // removes bo
LINQ remove where: Where plus ToList
LINQ has no RemoveAll. A LINQ query reads a sequence and produces a new one, so the pattern is to keep what you want and assign it back.
numbers = numbers.Where(n => n % 2 != 0).ToList(); // keeps the odd numbers
Use RemoveAll when you own the list and want it changed in place. Use Where when the source is an IEnumerable<T>, or when other code holds a reference to the original list and must not see the change.
Remove one list from another
To delete every element that appears in a second collection, combine RemoveAll with Contains. Put the second collection in a HashSet<T> when it is large. Contains on a hash set is O(1), and on a list it is O(n).
var toRemove = new HashSet<int> { 2, 4 }; numbers.RemoveAll(n => toRemove.Contains(n));
numbers.Except(toRemove).ToList() also works, with one difference. Except returns distinct elements, so duplicates in the first list collapse to one copy.
The foreach trap
Calling Remove inside a foreach over the same list throws InvalidOperationException on the next step of the loop. The enumerator records a version number and stops when the list changes under it. Two patterns are safe.
// Wrong: throws InvalidOperationException on the next iteration foreach (var n in numbers) if (n > 3) numbers.Remove(n); // Safe: walk backwards so a removal never shifts an unvisited element for (int i = numbers.Count - 1; i >= 0; i--) if (numbers[i] > 3) numbers.RemoveAt(i); // Best: one call, one pass numbers.RemoveAll(n => n > 3);
A forward for loop compiles and runs, but it skips elements. After RemoveAt(i), the element that was at i + 1 moves to i, and i++ steps over it.
Cost of each method
| Method | Removes | Cost |
|---|---|---|
Remove(item) | First equal element | O(n): a scan, then a shift |
RemoveAt(index) | Element at index | O(n - index); O(1) for the last element |
RemoveAll(predicate) | Every match | O(n), one pass |
RemoveRange(index, count) | A block | O(n) |
Clear() | Everything | O(n) |
Where().ToList() | Nothing; builds a new list | O(n) time plus O(n) memory |
One detail matters for class types. Remove compares with EqualityComparer<T>.Default, which is reference equality unless the class overrides Equals. A new object with the same field values will not match, so remove objects with RemoveAll on a property, or with the reference you already hold. Records, structs and strings compare by value, so Remove works on them as expected.
Key Takeaways
- Choose by what you know. A value:
Remove. A position:RemoveAt. A condition:RemoveAll. RemoveAllbelongs toList<T>, not to LINQ. LINQ filters withWhereand leaves the source unchanged.- Never remove inside
foreach. Walk the list backwards with aforloop, or callRemoveAll. - Removing from the middle costs O(n) because every later element shifts left. Grokking Data Structures for Coding Interviews explains why, and when a linked list or a hash set is the better choice.
- Practice the pattern under time pressure. The problems in Grokking the Coding Interview use in-place removal often, and a mock interview shows whether you choose
RemoveAllwithout pausing.

GET YOUR FREE
Coding Questions Catalog

$99

$197

$72