Start learning free

LESSON 98 OF 348 · ARRAYS I

Exceeding Array Bounds in Kotlin: IndexOutOfBoundsException

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, Kotlin throws an error:

val myArray = mutableListOf(50, 40, 30)

// Valid indices: 0, 1, 2
println(myArray[0]) // Output: 50
println(myArray[2]) // Output: 30

// Invalid indices: 3, 30, -1
println(myArray[3]) // raises IndexOutOfBounds error
println(myArray[30]) // raises IndexOutOfBounds error
println(myArray[-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

    KOTLIN · ARRAYS I

    Exceeding Array Bounds in Kotlin, explained

    What "out of bounds" means

    A list in Kotlin numbers its items from 0. A list with n items has the valid indices 0 to n - 1, and every other index is invalid. Reading or writing an item at an invalid index is called exceeding the bounds.

    Kotlin does not guess what you meant, and it does not return a default value. It stops the program with an IndexOutOfBoundsException that names the index and the size:

    val myArray = mutableListOf(50, 40, 30)
    
    println(myArray[0])   // 50
    println(myArray[2])   // 30
    println(myArray[3])   // throws: Index 3 out of bounds for length 3
    println(myArray[-1])  // throws: Index -1 out of bounds for length 3
    

    Crashing loudly is useful. A wrong index is a bug in your program, and an immediate error with the exact index is easier to fix than a silently wrong answer.

    Where the error comes from

    Most out-of-bounds errors are off-by-one mistakes. Counting items starts at 1, indexing starts at 0, and mixing them up lands you one step past the end. The classic form is using the size as an index:

    val ages = mutableListOf(14, 32, 50, 21)
    
    println(ages.size)     // 4
    println(ages[ages.size])  // throws: Index 4 out of bounds for length 4
    

    The list holds four items, so the last valid index is 3. The size is a count, not a position. The same mistake appears in loops that run one step too far, and when code reads from an empty list: an empty list has size 0, so no index is valid, not even 0.

    The exercise: print the last element

    The starting program should print the last element of ages, which is 21, but it crashes with IndexOutOfBoundsException instead. Fixing it takes three small steps:

    1. Read the error. It says index 4 is out of bounds for length 4. The program asked for the item at index 4, and the list only goes up to index 3.
    2. Find the line that uses that index. It is the println line, where the index is ages.size. The size is 4, so the index is 4.
    3. Subtract one. The last item sits at size - 1, which is 3 here. Writing ages.size - 1 keeps working when the list grows or shrinks, because the index follows the size.
    val ages = mutableListOf(14, 32, 50, 21)
    
    println(ages[ages.size - 1])
    

    This prints 21. Note that size is a property, so it is written without parentheses. The expression inside the brackets is calculated first, and then the item at that index is read.

    Safer ways to get items

    Computing size - 1 by hand is a good way to learn how indices work, but Kotlin offers tools that avoid the arithmetic:

    • ages.last() returns the last item, here 21.
    • ages.getOrNull(10) returns null instead of throwing when the index is invalid.
    • ages.indices is the range of valid indices. For the list above it is 0..3, and for (i in ages.indices) never goes past the end.

    The first one has a catch. On an empty list, last() also throws, just like list[list.size - 1], whose index is -1:

    val empty = mutableListOf<Int>()
    
    println(empty.lastOrNull())     // null
    println(empty[empty.size - 1])  // throws: Index -1 out of bounds for length 0
    

    lastOrNull() returns null for an empty list. Pick the safe version whenever the list might be empty.

    Arrays behave the same way

    A plain array created with arrayOf has the same rule: indices 0 to size - 1. Going outside it throws an ArrayIndexOutOfBoundsException, which is a more specific kind of IndexOutOfBoundsException, with the same message format:

    val arr = arrayOf(1, 2, 3)
    println(arr[3])  // throws: Index 3 out of bounds for length 3
    

    Common mistakes

    • Using size as the last index. The last index is size - 1.
    • Looping with 0..list.size. The range 0..n includes n, so the last pass reads one item too many. Use 0 until list.size or list.indices.
    • Forgetting the empty list. An empty list has no valid index at all, so list[0] already throws.
    • Counting from 1. The first item is list[0], and the third item is list[2].
    • Negative indices. Unlike some languages, Kotlin does not count from the end with -1. It throws.

    Key takeaways

    • Valid indices run from 0 to size - 1.
    • Any other index throws IndexOutOfBoundsException, and the message tells you the bad index and the size.
    • The last item is list[list.size - 1], or list.last() on a list that is not empty.
    • getOrNull and lastOrNull return null instead of throwing.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free