Valid and invalid indices
An array of size n has valid indices 0 through n - 1. Any other index is outside the array's bounds. Kotlin checks every access, so an invalid index never reads some nearby memory: the program throws an exception instead.
val ourArray = intArrayOf(50, 40, 30)
println(ourArray[0])
println(ourArray[2])
println(ourArray[3])
50
30
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
The first two lines work, then the third stops the program. The message names the bad index and the array's length. For an array, the exception is ArrayIndexOutOfBoundsException, a kind of IndexOutOfBoundsException. Indexes 30 and -1 fail the same way: -1 doesn't mean "the last element" in Kotlin.
Why this is a common bug
The mistake usually comes from the gap between a size and an index. An array of 3 elements has size 3, but the highest index is 2. Writing ourArray[ourArray.size] asks for the element after the last one.
The exercise
The starter code was meant to print the last element of ages but throws an exception:
val ages = intArrayOf(14, 32, 50, 21)
println(ages[ages.size])
The array has four elements, so ages.size is 4 and the valid indices are 0 to 3. The index 4 is one past the end, which is what fails. The last element is at ages.size - 1:
val ages = intArrayOf(14, 32, 50, 21)
println(ages[ages.size - 1])
This prints 21. Writing ages[3] would print the same here, but ages.size - 1 stays right if the array changes length. The shortcuts ages.last() and ages[ages.lastIndex] also give 21.
A loop that goes one step too far
Out-of-bounds errors often appear at the end of a loop. The range 0..ages.size includes the size, so the loop makes one pass too many:
val ages = intArrayOf(14, 32, 50, 21)
try {
for (i in 0..ages.size) {
println(ages[i])
}
} catch (e: ArrayIndexOutOfBoundsException) {
println("caught: " + e.message)
}
14
32
50
21
caught: Index 4 out of bounds for length 4
All four valid elements print, and then the access at index 4 throws. Catching the exception, as above, only demonstrates it; the real fix is the range 0 until ages.size.
Lists fail the same way
A MutableList checks its indices too. Reading index 5 of a two-item list throws IndexOutOfBoundsException with the message Index 5 out of bounds for length 2. The arrays in this lesson throw the more specific ArrayIndexOutOfBoundsException, which is a subclass of it.
Reading the error message
The exception text carries the two numbers you need: the index that was requested and the length of the array. In Index 4 out of bounds for length 4, the program asked for index 4 in an array of length 4. Compare the two: the index must be below the length. If it is equal, you are one past the end, as in the exercise. If it is much larger or negative, look for a calculation that produced the wrong number.
In the console you also see a stack trace, which lists the function calls that led to the error. The first line that names your own code is where to look.
Staying inside the bounds
- Compute the last index from the size. Use
size - 1instead of a typed number. - Loop with the right range.
for (i in 0 until ages.size)andfor (i in ages.indices)stop at the last valid index. A range written as0..ages.sizeincludes the size itself and fails on the last pass. - Watch for empty arrays. An empty array has no valid index, and
size - 1is -1. Checkages.isNotEmpty()before reading the last element. - Use a safe read when a miss is acceptable.
ages.getOrNull(10)returnsnullinstead of throwing.
Common mistakes
- Using the size as the index. This is the mistake the exercise shows.
- Assigning outside the bounds.
ages[4] = 7throws as well. An array never grows to fit a new index. - Catching the exception to hide a bug. It is better to fix the index than to wrap the access in
try.
Key takeaways
- Valid indices run from 0 to
size - 1. - An invalid index throws
ArrayIndexOutOfBoundsException, with a message naming the index and the length. array[array.size - 1]reads the last element.