Start learning free

LESSON 60 OF 348 · STRINGS I

Exceeding String Bounds in Kotlin: StringIndexOutOfBoundsException

Lesson Read the full explanation

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

Remember, the characters of a string are normally indexed from 0 to length - 1.

Any index which is strictly greater than length - 1 is invalid.

When you try to access a character with an invalid index, Kotlin throws a StringIndexOutOfBoundsException:

val car = "ford"

// Valid indices: 0, 1, 2, 3
println(car[0]) // Output: f
println(car[3]) // Output: d

// Invalid indices: 4, 30
println(car[4]) // StringIndexOutOfBoundsException
println(car[30]) // StringIndexOutOfBoundsException

This code would throw a StringIndexOutOfBoundsException: String index out of range: 4

In conclusion, we always want to double check our code to make sure that we don't exceed one string's bounds in our programs.


Assignment
Follow the Coding Tutorial and let's practice with string bounds!


Hint
Look at the examples above if you get stuck.


STEP 1 OF 1
    Ctrl + Enter to check

    KOTLIN · STRINGS I

    Exceeding String Bounds in Kotlin, explained

    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:

    1. Find the length. "I love pancakes" has 15 characters, counting the two spaces. So message.length is 15.
    2. Find the valid indexes. They are 0 to 14. The code asks for index 15, which is one past the end, so it throws.
    3. 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 length instead of length - 1 for the last character.
    • Looping one step too far. for (i in 0..text.length) includes the length itself. Use 0 until text.length or text.indices.
    • Indexing an empty string. ""[0] is out of bounds because there are no valid indexes at all. length - 1 is -1 there.
    • Forgetting negative values. If an index is computed, for example length - n, it can drop below 0 when n is too big.

    Key takeaways

    • Valid indexes run from 0 to length - 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.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free