Start learning free

LESSON 61 OF 415 · STRINGS I

Exceeding String Bounds in Swift: Avoiding the Index Crash

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 count - 1.

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

When you try to access a character with an invalid index, Swift crashes the program with an index out of range error:

var car = "ford"

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

// Invalid indices: 4, 30
print(Array(car)[4]) // Crashes: index out of range
print(Array(car)[30]) // Crashes: index out of range

This code would crash at the first invalid index, Array(car)[4], because there is no character there.

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

    SWIFT · STRINGS I

    Exceeding String Bounds in Swift, explained

    What goes wrong

    A string with count characters has valid positions numbered 0 to count - 1. Anything at or beyond count is out of bounds. In many languages that gives an exception you can catch. In Swift, reading a position that doesn't exist stops the program at once. There is no error to handle, so the only protection is to compute the position correctly.

    With a string, the position is a String.Index that you build from startIndex and an offset. The index endIndex is special: it marks the spot just after the last character. It is a valid index to compute, but there is no character there to read.

    let car = "ford"
    print(car[car.index(car.startIndex, offsetBy: 3)])
    print(car[car.index(car.startIndex, offsetBy: 4)])
    

    The first print shows d, the character at offset 3. The second uses offset 4, which is car.endIndex, so the program stops with a fatal error:

    Fatal error: String index is out of bounds
    

    An offset beyond the end, such as 9, fails too, already when index(_:offsetBy:) computes it. A negative offset from startIndex is also invalid. Converting the string to an array, as in Array(car)[4], doesn't help: reading a position past the end of an array also stops the program.

    The exercise

    The code should print the last character of message, which is s:

    let message = "I love pancakes"
    
    print(message[message.index(message.startIndex, offsetBy: message.count)])
    

    It offsets startIndex by message.count, which is 15. The valid offsets for this string are 0 to 14, so 15 is exactly endIndex, one past the last character. The program stops instead of printing.

    The fix is a single number. The last character is at offset count - 1:

    let message = "I love pancakes"
    
    print(message[message.index(message.startIndex, offsetBy: message.count - 1)])
    
    s
    

    Safer ways to get there

    You can often avoid computing the offset yourself:

    • message.last returns an optional Character?, which is nil for an empty string instead of crashing.
    • message.index(before: message.endIndex) steps back one character from the end. It is only valid when the string isn't empty.
    • index(_:offsetBy:limitedBy:) returns an optional index, nil when the offset would pass the limit:
    let message = "I love pancakes"
    let limit = message.endIndex
    
    if let idx = message.index(message.startIndex, offsetBy: 50, limitedBy: limit) {
        print(message[idx])
    } else {
        print("offset is past the end")
    }
    
    offset is past the end
    

    Be careful with that last form: an offset equal to count does produce an index, namely endIndex, so it is not nil, and reading a character through it still crashes. The limit only protects the index computation, not the read.

    When you know the string is non-empty, last! and the offset count - 1 give the same character.

    Common mistakes

    • Using count as the last offset. The last character is at count - 1.
    • Taking the last character of an empty string. With count equal to 0, count - 1 is -1, which is invalid. Check isEmpty first, or use last.
    • Reading at endIndex. It is a boundary, not a character. Use it as the end of a range such as start..<endIndex, but never subscript with it.
    • Counting from 1. The 5th character is at offset 4.

    Key takeaways

    • Valid offsets run from 0 to count - 1.
    • Reading an out-of-bounds position stops the program with a fatal error. It can't be caught.
    • Use last, first and prefix or suffix when the string might be short.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free