Back to Blog
C#

C# Array Loops: for vs foreach and More

Learn how to iterate over C# arrays effectively. Compare for and foreach, handle multidimensional and jagged arrays, and avoid common pitfalls.

C#Array Iterationforeachfor LoopPerformance
Diagram showing a for loop and a foreach loop iterating over a C# array, with index access on the left and direct element access on the right.

When you need to iterate over a C# array, the first decision is which loop construct to use. The two primary options are for and foreach. Both are valid, but they differ in syntax, flexibility, and runtime behavior. This article explains the tradeoffs and shows how to choose the right pattern for your scenario.

Basic Array Loops: for and foreach

The simplest way to iterate over an array is with foreach. It hides the index management and works with any IEnumerable<T>:

int[] numbers = { 10, 20, 30, 40 }; foreach (int number in numbers) { Console.WriteLine(number); }

The for loop gives you explicit control over the index and the loop condition:

for (int i = 0; i < numbers.Length; i++) { Console.WriteLine(numbers[i]); }

Both produce the same output. The difference becomes important when you need to access the index, modify array elements, or control the iteration order.

Choosing Between for and foreach

Use foreach when you only need to read each element and do not need the index. It is more concise and less error-prone because you do not manage the loop counter or the upper bound. For arrays, the compiler turns foreach into index-based code, so it does not add an enumerator or allocation overhead.

Use for when you need the index for any reason, such as:

  • Writing back to the array at the current position.
  • Skipping or repeating elements by adjusting the index.
  • Iterating in reverse order.
  • Accessing neighboring elements (for example, array[i-1] and array[i+1]).

Here is a common pattern that requires for: reversing an array in place.

int[] array = { 1, 2, 3, 4, 5 }; for (int i = 0; i < array.Length / 2; i++) { int temp = array[i]; array[i] = array[array.Length - 1 - i]; array[array.Length - 1 - i] = temp; }

foreach does not expose the index, and its iteration variable is read-only. Assigning to that variable is a compile-time error.

Modifying Array Elements During Iteration

Arrays are reference types, but the mutability of their elements depends on the element type. With foreach, you can change members of a reference type element, but you cannot replace the element itself. For example:

class Point { public int X; public int Y; } Point[] points = { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } }; foreach (Point p in points) { p.X = 10; // This works: p references the same object in the array. }

However, p = new Point(); inside the loop would not change the array. To replace elements, use for:

for (int i = 0; i < points.Length; i++) { points[i] = new Point { X = i, Y = i * 2 }; }

This distinction is a frequent source of confusion for developers new to C#.

Performance Characteristics of Array Loops

Both for and foreach produce similar compiled code for arrays. The JIT compiler can often remove bounds checks when the loop condition compares against array.Length. The main performance cost for large arrays is usually cache behavior, not the loop construct itself.

A foreach over a List<T> can have more overhead than over an array because it relies on the list's enumerator rather than direct index access. In modern .NET the difference is often small, but arrays avoid an enumerator entirely. Do not avoid foreach on arrays for performance reasons.

If you need maximum performance in a hot path, consider using a for loop with a local copy of the length:

int[] data = GetData(); int length = data.Length; for (int i = 0; i < length; i++) { Process(data[i]); }

This prevents the JIT from re-evaluating the Length property each iteration, though in practice the JIT often hoists it automatically. The measurable difference is negligible in most applications.

Handling Multidimensional and Jagged Arrays

C# has two types of multidimensional arrays: rectangular (int[,]) and jagged (int[][]). Looping over them requires different approaches.

For a rectangular array, you can use nested for loops with GetLength:

int[,] matrix = new int[3, 4]; for (int i = 0; i < matrix.GetLength(0); i++) { for (int j = 0; j < matrix.GetLength(1); j++) { matrix[i, j] = i * j; } }

foreach also works on rectangular arrays, but it returns elements in row-major order and does not give you the indices. If you need indices, use nested for loops.

Jagged arrays are arrays of arrays, so you can iterate with a foreach over the outer array and then a foreach over each inner array:

int[][] jagged = new int[3][]; jagged[0] = new int[] { 1, 2 }; jagged[1] = new int[] { 3, 4, 5 }; jagged[2] = new int[] { 6 }; foreach (int[] inner in jagged) { foreach (int value in inner) { Console.WriteLine(value); } }

Jagged arrays are more flexible because each row is an independent array and rows can have different lengths. The performance trade-off depends on access patterns: rectangular arrays use one contiguous block, while jagged arrays require an extra indirection.

Using Span<T> for Efficient Array Iteration

Span<T> is a ref struct that provides an allocation-free view over contiguous memory, including arrays. It is useful when you want to avoid copying subarrays or when working with slices.

int[] array = { 1, 2, 3, 4, 5, 6 }; Span<int> span = array.AsSpan(2, 3); // elements 3,4,5 foreach (int value in span) { Console.WriteLine(value); }

Span<T> also supports a for loop with index access, and it has a Length property. Because Span<T> is a ref struct, it cannot be boxed, stored in a class field, or used across await boundaries. For most array iteration scenarios, a simple for or foreach is sufficient; Span<T> becomes valuable when you need to pass slices without copying or when interoperating with native code.

Common Pitfalls and How to Avoid Them

One frequent mistake is trying to use the foreach iteration variable to replace an element. The variable is read-only, so assigning to it does not update the array. Use for when you need to replace or reorder elements. For collections such as List<T>, modifying the collection during foreach can throw InvalidOperationException; arrays are fixed-size, but the loop variable still cannot be reassigned.

Another pitfall is off-by-one errors in for loops. Always use < array.Length rather than <= to avoid IndexOutOfRangeException. For reverse iteration, use i >= 0 with a careful condition:

for (int i = array.Length - 1; i >= 0; i--) { // process array[i] }

When you need to iterate over a portion of an array, avoid Array.Copy or LINQ's Skip/Take if the data is large. Array.Copy duplicates data, and LINQ operators add allocation overhead. Use ArraySegment<T> or Span<T> to reference the original data without copying.

Finally, remember that foreach over an array that is null throws a NullReferenceException. Always check for null before iterating if the array comes from an external source.

Choosing the right array loop pattern comes down to whether you need the index, whether you need to replace elements, and which array shape you are working with. For simple read-only iteration, foreach is clean and efficient. For index-based manipulation, for gives you the control you need. Understanding these differences prevents subtle bugs and keeps your code maintainable.