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:
- 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.
- Find the line that uses that index. It is the
printlnline, where the index isages.size. The size is 4, so the index is 4. - Subtract one. The last item sits at
size - 1, which is 3 here. Writingages.size - 1keeps 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, here21.ages.getOrNull(10)returnsnullinstead of throwing when the index is invalid.ages.indicesis the range of valid indices. For the list above it is0..3, andfor (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
sizeas the last index. The last index issize - 1. - Looping with
0..list.size. The range0..nincludesn, so the last pass reads one item too many. Use0 until list.sizeorlist.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 islist[2]. - Negative indices. Unlike some languages, Kotlin does not count from the end with
-1. It throws.
Key takeaways
- Valid indices run from
0tosize - 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], orlist.last()on a list that is not empty. getOrNullandlastOrNullreturnnullinstead of throwing.