Start learning free

LESSON 96 OF 347 · ARRAYS I

Exceeding List Bounds in C#: ArgumentOutOfRangeException

Lesson Read the full explanation

When accessing ArrayList data with indices, the most common problem we can run into is exceeding the ArrayList's bounds.

Remember, the items of an ArrayList are indexed from 0 to size - 1. Any index which is not in this range is invalid.

When you try to access an item with an invalid index, C# throws an error:

List<int> ourArray = new List<>(List.of(50, 40, 30));

// Valid indices: 0, 1, 2
Console.WriteLine(myArray.get(0)); // Output: 50
Console.WriteLine(myArray.get(2)); // Output: 30

// Invalid indices: 3, 30, -1
Console.WriteLine(myArray.get(3)); // raises IndexOutOfBounds error
Console.WriteLine(myArray.get(30)); // raises IndexOutOfBounds error
Console.WriteLine(myArray.get(-1)); // raises IndexOutOfBounds error

As programmers, we always want to make sure that we don't exceed one ArrayList's bounds in our programs.


Assignment
Follow the Coding Tutorial and let's play with some arrays.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 1
    Ctrl + Enter to check

    C# · ARRAYS I

    Exceeding Array Bounds in C#, explained

    The valid range

    A List<T> with Count items has the valid indexes 0 through Count - 1. Any other index is out of bounds. The program checks the index each time it runs, and an invalid one throws an ArgumentOutOfRangeException.

    List<int> ourArray = new List<int> {50, 40, 30};
    
    // Valid indexes: 0, 1, 2
    Console.WriteLine(ourArray[0]);  // 50
    Console.WriteLine(ourArray[2]);  // 30
    
    // Invalid indexes: 3 and above, and anything negative
    Console.WriteLine(ourArray[3]);  // throws ArgumentOutOfRangeException
    

    The message says Index was out of range. Must be non-negative and less than the size of the collection. A negative index fails the same way, because C# lists don't count backwards from the end.

    Don't confuse this with arrays. An invalid index on an array throws IndexOutOfRangeException, while a list throws ArgumentOutOfRangeException. Both mean the same mistake, and both are thrown at run time, not caught by the compiler. If the exception isn't caught, the program stops at that line.

    The exercise, step by step

    The starter code should print the last element of ages but crashes:

    List<int> ages = new List<int>(new[] {14, 32, 50, 21});
    
    Console.WriteLine(ages[ages.Count]);
    
    1. Find the index. ages.Count is 4, but the indexes are 0, 1, 2 and 3. Index 4 is one past the end.
    2. Step back by one. The last item is at Count - 1, which is 3. Using ages.Count - 1 reads 21.

    The fixed program:

    List<int> ages = new List<int>(new[] {14, 32, 50, 21});
    
    Console.WriteLine(ages[ages.Count - 1]);
    

    It prints:

    21
    

    Guarding against the error

    You can check the index before using it. The check has two parts, because both ends of the range matter:

    List<int> ages = new List<int> {14, 32, 50, 21};
    int index = 4;
    
    if (index >= 0 && index < ages.Count) {
        Console.WriteLine(ages[index]);
    } else {
        Console.WriteLine("No such position");
    }
    

    This prints No such position. You could also catch the exception with try and catch (ArgumentOutOfRangeException), but a plain check is simpler and shows your intent.

    When the exception does happen, read its text first. It names the exception type, and the stack trace points to the line that used the bad index. Then work out which index you passed and compare it with the list's count. Remember that the count can change while the program runs, so the same line may work at one moment and fail at another.

    Because the check happens at run time, a program can work for months and fail the first time it receives an unusual input, such as an empty list. Testing small and empty inputs is therefore the best way to find these bugs early.

    Common causes

    • Using the count as an index. The count is a total, and the last index is one less.
    • Loops that go one step too far. A condition of i <= list.Count reaches the invalid index. Use i < list.Count.
    • Empty lists. With a count of 0 there is no valid index, and list[list.Count - 1] asks for index -1.
    • Lists that shrink. The valid range changes as items are removed. An index that was fine before a RemoveAt can be out of range afterwards.
    • Assigning to a new position. list[list.Count] = 5; also throws. A list grows with Add, not by assigning past the end.

    Why C# checks bounds

    Some languages let a program read past the end of a collection and silently return whatever happens to be in memory. C# never does that. Every list access is checked, so a mistake produces a clear exception with a line number, not wrong results that appear much later. The check is the reason the error message exists in the first place.

    Things to try

    Try these on a four-item list to see which ones throw. The index 0 works, the index 3 works, and the indexes 4 and -1 throw. Then try list[list.Count - 1] on a list created with new List<int>() and watch it fail at index -1.

    Key takeaways

    • Valid list indexes run from 0 to Count - 1.
    • An invalid index throws ArgumentOutOfRangeException at run time.
    • To get the last item, use list[list.Count - 1], after checking that the list isn't empty.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free