Start learning free

LESSON 102 OF 414 · ARRAYS I

Exceeding Array Bounds in Scala: IndexOutOfBoundsException

Lesson Read the full explanation

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

Remember, the items of an ArrayBuffer are indexed from 0 to size - 1. Any index which is not in this range is invalid.

When you try to access an item with an invalid index, Scala throws an exception:

val ourArray = scala.collection.mutable.ArrayBuffer(50, 40, 30)

// Valid indices: 0, 1, 2
println(ourArray(0)) // Output: 50
println(ourArray(2)) // Output: 30

// Invalid indices: 3, 30, -1
println(ourArray(3)) // throws IndexOutOfBoundsException
println(ourArray(30)) // throws IndexOutOfBoundsException
println(ourArray(-1)) // throws IndexOutOfBoundsException

As programmers, we always want to make sure that we don't exceed one ArrayBuffer's bounds in our programs.


Assignment
Follow the Coding Tutorial and let's play with some arrays.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 1
    Ctrl + Enter to check

    SCALA · ARRAYS I

    Exceeding Array Bounds in Scala, explained

    What "out of bounds" means

    A sequence with n elements has valid indices 0 through n - 1. Any other index points at nothing: -1 is before the first element, and n is one past the last. In Scala, asking for such an element does not return a default or null. It throws an exception, and a program that doesn't catch it stops.

    This lesson uses an ArrayBuffer, Scala's growable, mutable sequence, written with its full name scala.collection.mutable.ArrayBuffer because it isn't imported by default. Everything below applies equally to Array, List and Vector, which all use parentheses for indexing.

    What the error looks like

    For a buffer holding four elements, index 4 and index -1 are both invalid:

    val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
    
    println(ages(0))   // 14
    println(ages(3))   // 21
    println(ages(4))   // throws
    println(ages(-1))  // throws
    

    The two failing lines stop the program with these messages:

    java.lang.IndexOutOfBoundsException: 4 is out of bounds (min 0, max 3)
    java.lang.IndexOutOfBoundsException: -1 is out of bounds (min 0, max 3)
    

    The message tells you the bad index and the valid range, which is usually enough to find the mistake. A plain Array throws the closely related ArrayIndexOutOfBoundsException, with the text Index 3 out of bounds for length 3 for a three-element array.

    The lesson's exercise

    The starting program should print the last element of ages, which is 21, but it crashes:

    val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
    
    println(ages(ages.size))
    

    The buffer has four elements, so ages.size is 4. The valid indices are 0 to 3, which means the program asks for index 4, one past the end. This is the classic off-by-one error: the size is a count, but an index counts from zero, so the last index is always one less than the size.

    The fix is to subtract one:

    val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
    
    println(ages(ages.size - 1))
    

    Now the index is 3 and the program prints 21. Because the index is computed from size, it keeps pointing at the last element even after the buffer grows or shrinks.

    Guarding against bad indices

    Subtracting one is not enough when the sequence might be empty. For an empty buffer, size - 1 is -1, which is itself invalid:

    java.lang.IndexOutOfBoundsException: -1 is out of bounds (min 0, max -1)
    

    Scala offers safer ways to read an element that might not exist:

    • ages.lift(10) returns an Option: Some(element) for a valid index and None for an invalid one, so nothing is thrown. For example, ages.lift(1) is Some(32) and ages.lift(10) is None.
    • ages.lastOption and ages.headOption give the last and first element as an Option. On an empty buffer they are None.
    • An explicit check, if (i >= 0 && i < ages.size), works when you need to run different code for the two cases.
    val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
    
    println(ages.lift(1))      // Some(32)
    println(ages.lift(10))     // None
    println(ages.lastOption)   // Some(21)
    

    Common mistakes

    • Using size as an index. It is one past the last valid index.
    • Looping one step too far. A loop such as for (i <- 0 to ages.size) includes ages.size and fails on the last pass. Use 0 until ages.size, which stops before the end.
    • Negative indices. Scala does not count from the end the way some languages do. ages(-1) is an error, not the last element.
    • Assuming the buffer is not empty. Check the size, or use lastOption, when the data may be empty.

    Key takeaways

    • Valid indices run from 0 to size - 1.
    • An invalid index throws IndexOutOfBoundsException; nothing is returned and nothing is silently skipped.
    • The last element is ages(ages.size - 1).
    • lift and lastOption turn a missing element into None instead of a crash.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free