Valid and invalid indexes
A string of length n has characters at indexes 0 to n - 1. Any other index is outside the string's bounds. That includes indexes that are too large (n or more) and negative ones. Kotlin has no "count from the end" indexing like some languages, so text[-1] is just another invalid index.
Reading a character with an invalid index throws a StringIndexOutOfBoundsException. On the JVM this exception comes from the underlying Java string, and it extends IndexOutOfBoundsException.
val car = "ford"
// Valid indexes: 0, 1, 2, 3
println(car[0]) // f
println(car[3]) // d
// Invalid indexes: 4, 30, -1
println(car[4]) // throws
The first two lines print f and d. The third println stops the program with an error that ends like this:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Index 4 out of bounds for length 4
The message is useful: it names the bad index and the string's length. For car[30] it would say Index 30 out of bounds for length 4, and for car[-1] it says Index -1 out of bounds for length 4.
The exception is thrown while the program runs, not when it compiles. The compiler cannot tell what index a variable will hold, so code like this builds without any warning and fails only when that line is reached. Everything printed before it still appears, which is why the first two lines of the example print normally.
The classic mistake: using length as the index
The most common way to cross the boundary is to treat the length as the position of the last character. The length of "ford" is 4, but its last index is 3. The length is always one more than the last index.
The exercise
The lesson starts with a program that should print the last character of message, which is s, but crashes instead:
val message = "I love pancakes"
println(message[message.length])
Walk through it:
- Find the length.
"I love pancakes"has 15 characters, counting the two spaces. Somessage.lengthis 15. - Find the valid indexes. They are 0 to 14. The code asks for index 15, which is one past the end, so it throws.
- Move one step back. The last character is at
message.length - 1, which is 14.
val message = "I love pancakes"
println(message[message.length - 1])
This prints s.
Safer ways to access characters
When you are not sure an index is valid, you have choices besides letting the program crash.
You can check first, because the valid range is easy to test:
val text = "ford"
val i = 4
if (i >= 0 && i < text.length) {
println(text[i])
} else {
println("index $i is out of bounds")
}
Or you can use getOrNull, which returns null instead of throwing, and combine it with the safe-call and Elvis operators:
val text = "ford"
println(text.getOrNull(4)) // null
println(text.getOrNull(2)) // r
println(text.getOrNull(4) ?: '?') // ?
For the last character specifically, text.last() avoids the arithmetic, although it throws a NoSuchElementException on an empty string. text.lastOrNull() returns null in that case.
Other places strings go out of bounds
Single-character access is not the only way. substring checks its bounds too. For "JavaScript", which has 10 characters, substring(4, 20) throws a StringIndexOutOfBoundsException with the message Range [4, 20) out of bounds for length 10. The helpers take, drop and takeLast are forgiving by comparison: they return as much as the string has.
Reading the error when it happens
When the exception reaches the top of the program, Kotlin prints its name, the message and a list of the calls that led to it. The last line of that list that belongs to your code points at the line that used the bad index. Start there, work out the string's length, and compare it with the index the line used. In nearly every case one of the two is off by one.
You can also catch the exception with try and catch, but for a plain index check it is usually clearer to test the index first, and let real surprises crash loudly so you can fix them.
Common mistakes
- Using
lengthinstead oflength - 1for the last character. - Looping one step too far.
for (i in 0..text.length)includes the length itself. Use0 until text.lengthortext.indices. - Indexing an empty string.
""[0]is out of bounds because there are no valid indexes at all.length - 1is -1 there. - Forgetting negative values. If an index is computed, for example
length - n, it can drop below 0 whennis too big.
Key takeaways
- Valid indexes run from
0tolength - 1; everything else throws. - The error appears at run time, with a message naming the index and the length.
- The last character is
text[text.length - 1]. - Check the index, or use
getOrNull, when it might be invalid.