Introduction
IndexOutOfRangeException occurs when you access an array or span with an index that is negative or greater than or equal to its length. ArgumentOutOfRangeException occurs when you pass an invalid index to a collection method like List<T>[index] or string.Substring(). Both mean the same thing: you are trying to access an element that does not exist. The fix is to check bounds before accessing, use zero-based indexing correctly, and validate loop conditions.
IndexOutOfRangeException
Thrown when accessing arrays, Span<T>, or similar fixed-size structures:
1int[] numbers = { 10, 20, 30 };
2
3// Valid indices: 0, 1, 2
4Console.WriteLine(numbers[0]); // 10
5Console.WriteLine(numbers[2]); // 30
6
7// IndexOutOfRangeException — index 3 does not exist
8Console.WriteLine(numbers[3]);
9
10// IndexOutOfRangeException — negative index
11Console.WriteLine(numbers[-1]);
ArgumentOutOfRangeException
Thrown by List<T>, string, and other collection types:
1var list = new List<string> { "a", "b", "c" };
2
3// ArgumentOutOfRangeException — index 3 is out of range
4Console.WriteLine(list[3]);
5
6string text = "hello";
7
8// ArgumentOutOfRangeException — startIndex is beyond string length
9string sub = text.Substring(10);
Common Causes
Off-by-One Error in Loops
1int[] arr = { 1, 2, 3, 4, 5 };
2
3// WRONG: <= uses arr.Length (5), but max valid index is 4
4for (int i = 0; i <= arr.Length; i++)
5{
6 Console.WriteLine(arr[i]); // Crashes when i == 5
7}
8
9// CORRECT: < uses strict less-than
10for (int i = 0; i < arr.Length; i++)
11{
12 Console.WriteLine(arr[i]);
13}
Empty Collection Access
1var list = new List<int>();
2
3// WRONG: accessing first element of empty list
4int first = list[0]; // ArgumentOutOfRangeException
5
6// CORRECT: check count first
7if (list.Count > 0)
8{
9 int first = list[0];
10}
11
12// Or use LINQ
13int? first = list.FirstOrDefault();
Hardcoded Index Assumptions
1string[] parts = line.Split(',');
2
3// WRONG: assuming there are always 3 parts
4string third = parts[2]; // IndexOutOfRangeException if fewer than 3 parts
5
6// CORRECT: validate length
7if (parts.Length >= 3)
8{
9 string third = parts[2];
10}
Multi-Dimensional Array Errors
1int[,] grid = new int[3, 4]; // 3 rows, 4 columns
2
3// WRONG: swapped row/column dimensions
4for (int i = 0; i < 4; i++) // Should iterate rows (3)
5 for (int j = 0; j < 3; j++) // Should iterate columns (4)
6 grid[i, j] = 1; // Crashes when i == 3
7
8// CORRECT: use GetLength for each dimension
9for (int i = 0; i < grid.GetLength(0); i++) // Rows
10 for (int j = 0; j < grid.GetLength(1); j++) // Columns
11 grid[i, j] = 1;
Prevention Strategies
Bounds Checking
1// Safe array access with bounds check
2public static T SafeGet<T>(T[] array, int index, T defaultValue = default)
3{
4 if (index >= 0 && index < array.Length)
5 return array[index];
6 return defaultValue;
7}
8
9int[] arr = { 10, 20, 30 };
10int value = SafeGet(arr, 5, -1); // Returns -1 instead of throwing
Using LINQ
1var list = new List<int> { 1, 2, 3 };
2
3// Safe alternatives to direct indexing
4int first = list.FirstOrDefault(); // 1 (or 0 if empty)
5int last = list.LastOrDefault(); // 3 (or 0 if empty)
6int atIndex = list.ElementAtOrDefault(10); // 0 (out of range returns default)
Pattern Matching (C# 11+)
1int[] arr = { 1, 2, 3 };
2
3// List pattern for safe destructuring
4if (arr is [var first, var second, ..])
5{
6 Console.WriteLine($"First: {first}, Second: {second}");
7}
Range and Index Operators (C# 8+)
1int[] arr = { 10, 20, 30, 40, 50 };
2
3int last = arr[^1]; // 50 (last element)
4int secondLast = arr[^2]; // 40
5int[] slice = arr[1..3]; // { 20, 30 }
6
7// Still throws if index is out of range
8// int bad = arr[^10]; // IndexOutOfRangeException
Debugging Tips
1// Log the index and collection size
2try
3{
4 var item = collection[index];
5}
6catch (ArgumentOutOfRangeException ex)
7{
8 Console.WriteLine($"Index: {index}, Count: {collection.Count}");
9 Console.WriteLine(ex.Message);
10 throw;
11}
12
13// Use conditional breakpoints in the debugger:
14// Break when: index >= array.Length || index < 0
String-Specific Cases
1string text = "Hello";
2
3// Substring bounds
4string sub1 = text.Substring(0, 5); // "Hello" — OK
5// string sub2 = text.Substring(0, 10); // ArgumentOutOfRangeException
6
7// Safe substring
8string SafeSubstring(string s, int start, int length)
9{
10 if (start >= s.Length) return "";
11 return s.Substring(start, Math.Min(length, s.Length - start));
12}
13
14// Char access
15char c = text[0]; // 'H'
16// char bad = text[10]; // IndexOutOfRangeException
Common Pitfalls
Using <= instead of < in loop conditions: Arrays and lists are zero-indexed, so valid indices range from 0 to Length - 1. Using i <= arr.Length iterates one step too far. Always use i < arr.Length.
Assuming Split() returns a minimum number of elements: "a,b".Split(',') returns 2 elements, but "a".Split(',') returns 1. Always check parts.Length before accessing specific indices.
Accessing the last element with arr[arr.Length]: The last valid index is arr.Length - 1, not arr.Length. Use arr[^1] (C# 8+) or arr[arr.Length - 1] for the last element.
Not checking for empty collections before indexing: list[0] on an empty list throws ArgumentOutOfRangeException. Always check list.Count > 0 or use list.FirstOrDefault() for safe access.
Confusing row and column indices in 2D arrays: array[row, col] — the first dimension is rows, the second is columns. Swapping them accesses out-of-bounds memory when the dimensions differ.
Summary
IndexOutOfRangeException — arrays, spans (index < 0 or index >= length)
ArgumentOutOfRangeException — lists, strings, collections (invalid argument to a method)
Always use < Length (not <= Length) in loop conditions
Check collection size before accessing specific indices
Use FirstOrDefault(), ElementAtOrDefault(), or bounds-checking helpers for safe access
Use arr[^1] (C# 8+) for safe last-element access syntax