C#
List
Iteration
RemoveItems
Programming

C List - Removing items while looping / iterating

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Removing elements from a List<T> while iterating is a frequent source of runtime errors and skipped items. The wrong loop shape can invalidate enumeration or mis-handle shifted indexes. Safe removal depends on choosing the right mutation pattern for your use case.

Why foreach Removal Throws

foreach uses an enumerator that assumes collection structure does not change during iteration. Removing items invalidates the enumerator and throws InvalidOperationException.

csharp
1using System;
2using System.Collections.Generic;
3
4var values = new List<int> { 1, 2, 3, 4, 5 };
5
6try
7{
8    foreach (var v in values)
9    {
10        if (v % 2 == 0)
11        {
12            values.Remove(v);
13        }
14    }
15}
16catch (InvalidOperationException ex)
17{
18    Console.WriteLine(ex.Message);
19}

So do not structurally mutate list inside foreach.

Best Default: RemoveAll

For predicate-based removals, RemoveAll is concise and safe.

csharp
1using System;
2using System.Collections.Generic;
3
4var values = new List<int> { 1, 2, 3, 4, 5, 6 };
5int removed = values.RemoveAll(x => x % 2 == 0);
6
7Console.WriteLine($"removed={removed}");
8Console.WriteLine(string.Join(",", values));

This is usually the best in-place option when condition is straightforward.

Reverse for Loop for Index-Sensitive Logic

When deletion logic depends on index context, iterate backward.

csharp
1using System;
2using System.Collections.Generic;
3
4var words = new List<string> { "keep", "tmp", "keep2", "tmp2" };
5
6for (int i = words.Count - 1; i >= 0; i--)
7{
8    if (words[i].StartsWith("tmp"))
9    {
10        words.RemoveAt(i);
11    }
12}
13
14Console.WriteLine(string.Join(",", words));

Backward iteration avoids index-shift skip bugs after removal.

Controlled Forward while Loop

A forward loop can work if you only increment index when no removal occurs.

csharp
1using System;
2using System.Collections.Generic;
3
4var nums = new List<int> { 2, 2, 3, 2, 4 };
5int i = 0;
6
7while (i < nums.Count)
8{
9    if (nums[i] == 2)
10    {
11        nums.RemoveAt(i);
12    }
13    else
14    {
15        i++;
16    }
17}
18
19Console.WriteLine(string.Join(",", nums));

This pattern is useful for stateful scans where RemoveAll is too limited.

Immutable Filtering Alternative

If mutating original list is undesirable, create a filtered list instead.

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5var source = new List<int> { 1, 2, 3, 4, 5, 6 };
6var filtered = source.Where(x => x % 2 != 0).ToList();
7
8Console.WriteLine(string.Join(",", source));
9Console.WriteLine(string.Join(",", filtered));

This is often easier in functional pipelines, but allocates new memory.

Performance Considerations

RemoveAt shifts elements, so repeated removals can be costly for very large lists. RemoveAll is often more efficient than many individual RemoveAt calls because it compacts in one pass.

For heavy workloads:

  • Prefer batch predicate removal where possible.
  • Separate selection and mutation phases.
  • Profile before introducing custom structures.

In many applications, UI redraw or persistence overhead is larger than list mutation cost.

Decision Guide

Choose pattern by requirement:

  • Simple predicate and in-place mutation uses RemoveAll.
  • Index-aware logic uses reverse for.
  • Stateful forward scan uses controlled while.
  • Preserve source list uses LINQ filtering.

Using one clear pattern per method improves readability and correctness.

Common Pitfalls

  • Removing inside foreach and hitting enumerator invalidation.
  • Forward loops that increment blindly and skip shifted elements.
  • Using value-based removal when duplicate occurrence control is required.
  • Mixing business side effects and mutation logic in one opaque loop.
  • Reallocating filtered lists unnecessarily in hot paths.

Summary

  • Do not mutate List<T> structure during foreach iteration.
  • Use RemoveAll as default for predicate-based removals.
  • Use reverse iteration for index-sensitive deletions.
  • Use immutable filtering when original data should remain unchanged.
  • Account for index shifting explicitly in manual loop-based removal logic.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.