C#
List
Iteration
RemoveItems
Programming

C List - Removing items while looping / iterating

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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.

Course illustration
Course illustration

All Rights Reserved.