Concepts / Array Index Bounds and Exceptions

Array Index Bounds and Exceptions

A dynamic array lets a collection grow and shrink while the program runs, and the class design hides the internal array behind operations such as add, remove, get, set, and size.

  • Programming
Interactive lab

Try it: Indexing and Slicing

How Python indexes a sequence from 0 (or from the end with negative numbers) and how a slice start:stop:step picks positions — with the exact CPython rules for omitted and out-of-range bounds.

How it works

  1. Positions run 0 … len−1; negative positions count back from the end (−1 is the last).
  2. An index outside the string raises IndexError.
  3. A slice resolves start and stop (omitted → the ends; out of range → clamped), then walks by step, stopping before stop.
  4. A negative step walks backwards; a step of 0 is a ValueError.

Default run (7 steps): 'PYTHONIC'[1:6:2]: a slice copies characters from start up to (not including) stop, moving by step. … 'PYTHONIC'[1:6:2] → 'YHN' (positions 1, 3, 5).

Simplified: Works on a short string (up to 16 printable ASCII characters); lists slice by exactly the same rules.

Educational simulation

Loading the simulation…

Interactive lab

Try it: try / except / else / finally

How an exception jumps out of a try block to the first matching except clause, when else runs, and why finally always runs.

How it works

  1. Statements in try run until one raises an exception; the rest of try is skipped.
  2. Python checks the except clauses in order and runs the first one whose type matches.
  3. If nothing was raised, the else block runs instead.
  4. finally runs in every case.

Default run (9 steps): Call safe_divide('4'). … Return 25.0. Output was: Worked: 25.0 / Done.

Simplified: One fixed function; int() and / are modelled exactly (including how Python prints floats), but nothing is executed as Python.

Educational simulation

Loading the simulation…

Why a Fixed-Length Array Is Not Enough

Imagine a game that keeps a roster of the players currently taking part in a match. Players join partway through, and leave when they are eliminated. The roster has to reflect those changes at every moment. An ordinary array can hold the players, but its length is fixed when it is created. If you choose a length that is too small, you run out of room. If you choose one that is too large, you waste space and still have to keep track of which slots are actually in use.

A dynamic array solves this by letting the collection itself grow and shrink, so the programmer does not have to guess a size in advance. This article builds a growable collection of ints, a Java class named DynamicArrayOfInt that stores its items in a plain array and wraps that array in operations a caller can use safely. Its central question is what happens when someone asks for a position that does not hold a value.

What do you think happens?

A dynamic array currently holds 5 items at positions 0 through 4. Its internal storage has room for 8 values. If you call get(5), what happens?

  • It returns 0 because the storage slot at position 5 exists

  • It throws an ArrayIndexOutOfBoundsException

  • It returns the value stored in the spare slot

Reveal answer
Answer:

It throws an ArrayIndexOutOfBoundsException

The class counts its items, not its storage slots. Position 5 is at or beyond the item count of 5, so the class rejects it even though the internal array has room there. The rest of this article explains why.

Capacity Versus Size

The class holds two different numbers, and most early bugs come from mixing them up. The first is the length of the internal array, which is the number of slots that exist. The second is the item count, which records how many of those slots currently hold meaningful values. This article calls the first the capacity and the second the size. In the class, the array is stored in a field named items, and the count is stored in a field named itemCt. Because the array is usually only partly full, items.length and itemCt are usually different numbers.

slot 07slot 13slot 29slot 3unusedslot 4unusedslot 5unusedslot 6unusedslot 7unused
Which slots of the internal array hold real values, and which hold unused space, when the capacity is 8 and the size is 3?

The diagram shows a capacity of 8 and a size of 3. Slots 0 through 2 hold real values, and the remaining five slots are spare room. The class relies on itemCt, not on items.length, to know how much of the array is meaningful. The capacity is the storage that exists; the size is the part of that storage the collection is using.

The Five Operations and Which Ones Change the Count

A useful dynamic array needs a small, fixed set of operations. Each one is an instance method of the class, so a caller writes something like roster.add(7) rather than manipulating the internal array directly. The five essential operations are add, remove, get, set, and size. Their roles are these: add places a new value at the end of the collection, remove deletes the value at a position, get reads the value at a position, set replaces the value at a position, and size reports how many items the collection holds.

Only add and remove change how many items the collection holds. get, set, and size leave the count alone. This split is useful when reading the class: any method that touches itemCt is a method whose effect on the collection's shape you need to think about carefully.

Tracing a Sequence of Calls

The best way to see how these operations interact is to trace them one at a time. Follow the state after each call: the size, the values in the positions that hold items, and the capacity. This example starts with an empty collection whose capacity is 8.

Worked example

Tracing add, set, remove, get, and size

Starting from an empty DynamicArrayOfInt with capacity 8, perform these calls in order: add(7), add(3), add(9), set(1, 4), remove(0), get(1), get(2), size(). Track the size and the contents after each step, and decide what each get returns.

  1. 1
    After add(7), add(3), add(9)

    Each add finds spare room, stores its value at position itemCt, and increases itemCt by one. The contents are 7, 3, 9 at positions 0, 1, 2, and the size is 3. The capacity is still 8 because the array was never full.

  2. 2
    After set(1, 4)

    set names position 1, which is valid because 1 is less than the size of 3. It replaces the value 3 with 4. The contents are now 7, 4, 9. set does not change the size, so it stays at 3.

  3. 3
    After remove(0)

    remove deletes the value at position 0. The later values slide one position toward the front, so 4 moves to position 0 and 9 moves to position 1. Then itemCt drops from 3 to 2. The contents are 4, 9, and the size is 2. The capacity remains 8, because remove never shrinks the internal array.

  4. 4
    get(1)

    Position 1 is valid because it is less than the size of 2. The value stored there is 9, so get(1) returns 9.

  5. 5
    get(2)

    Position 2 is not valid now. The valid positions are 0 and 1, because the size is 2. The check fails and the class throws an ArrayIndexOutOfBoundsException. The slot at position 2 still physically exists in the internal array, but it holds no item.

  6. 6
    size()

    size reports the item count, which is 2. It does not report the capacity of 8.

Result

After the sequence, the collection holds 4 and 9 with size 2 and capacity 8. get(1) returned 9. get(2) threw an ArrayIndexOutOfBoundsException, and size() returns 2.

position 07position 03position 13position 19position 29position 2unusedsize3size2
After remove(0) on a collection holding 7, 3, 9, which values occupy which positions, and what is the size?

The before and after columns show what remove does. Every later value moved one position toward the front, and the count dropped by one. The position that used to hold the last value is no longer part of the collection, even though the storage slot is still there.

Why the Valid Range Stops at Size Minus One

Every operation that names a position has to decide what to do with a position that holds no value. The class throws an ArrayIndexOutOfBoundsException. The check is the same in get, set, and remove: the index is rejected if it is negative, or if it is greater than or equal to itemCt. The important detail is the upper limit. Because itemCt counts the items, the valid positions run from 0 through itemCt minus 1.

Formula, step by step
  1. 1
    The lower bound is 0, because positions start at zero.
  2. 2
    The upper bound is size minus 1, because the size counts the items and the last item sits one position before the count.
  3. 3
    Any index outside this range, whether negative or equal to or greater than the size, causes an ArrayIndexOutOfBoundsException in get, set, and remove.
index 0validindex 1validindex 2validindex 3validindex 4validindex 5out of bounds
For a dynamic array with size 5, which positions are valid indices, and why is index 5 rejected even though the internal array has room for it?

With a size of 5, the valid indices are 0, 1, 2, 3, and 4. Index 5 is rejected. The slot at position 5 may physically exist in the internal array, especially when the capacity is larger than the size, but it holds no item. The class checks the count, not the storage length, so that slot is treated as illegal.

How Add Grows the Storage

The class behaves as if it can hold any number of items, but its internal array has a fixed length. The growth happens inside add. Before writing a new value, add checks whether itemCt has reached items.length. If it has, the array is full. The method then asks Arrays.copyOf for a new array with twice the length, copies the existing values into it, and makes items refer to the new array. Only after that does add store the new value at position itemCt and increase itemCt by one.

Because every add that finds a full array doubles the capacity, the storage grows as needed and the caller never has to resize anything. Suppose the capacity is 8 and all 8 slots hold values. A ninth call to add first copies the eight old values into a new array of length 16, places the ninth value at position 8, and sets the size to 9. The eight old values survive the move, the new value lands in the first free position, and the remaining slots of the larger array wait to be used. The doubling rule is the one the class applies; the sizes in this example are illustrative values chosen to show it.

Remove Shifts Values but Keeps the Storage

Removal works in the opposite direction, and it is worth noticing what it does not do. The remove method slides every later value one position toward the front, then decreases itemCt by one. It never shrinks the internal array. The capacity stays what it was, and only the count changes. A dynamic array can therefore grow its storage, but the code in this class does not give that storage back after items are removed.

OperationChanges the sizeChanges the capacityEffect on valid range
addYes, increases by oneYes, doubles only when the array is fullUpper limit grows by one
removeYes, decreases by oneNoUpper limit shrinks by one
getNoNoPosition must be below size
setNoNoPosition must be below size
sizeNoNoReports the current size

How each operation affects the count, the storage, and the valid index range

Mistakes Beginners Make with Bounds

  • Common mistake

    Using the capacity as the upper limit for indices

    What it looks like

    A collection of size 5 has a capacity of 8, so a learner tries get(7) and expects a value.

    Why it fails

    The class checks the item count. Positions at or beyond the size hold no item, even if the internal array has room for them.

    Do this instead

    Compare the index against size, and accept only indices from 0 through size minus 1.

  • Common mistake

    Off-by-one error on the upper limit

    What it looks like

    A loop reads get(size) on the last pass, expecting the final value.

    Why it fails

    The last item sits at position size minus 1. Position size is the first position beyond the collection.

    Do this instead

    Use the range 0 through size minus 1, and recheck the boundary when a loop ends.

  • Common mistake

    Assuming remove frees the internal storage

    What it looks like

    After removing many items, a learner expects the capacity to drop back toward the size.

    Why it fails

    remove only shifts values and lowers the count. The internal array is never shrunk.

    Do this instead

    Track size and capacity separately. Expect capacity to stay the same after removals.

  • Common mistake

    Forgetting that set does not change the size, so a learner expects set to add a new item at an index beyond the size

Practice Tracing and Predicting

MEDIUM

A DynamicArrayOfInt starts empty with capacity 8. Perform these calls in order: add(4), add(6), add(8), remove(1), add(10). Then state the size, the contents of the valid positions, and whether get(2) would throw an exception.

Hints
  • Track the size after every call, because only add and remove change it.
  • After remove(1), the value at position 1 is removed and later values shift toward the front.
  • The valid positions run from 0 through size minus 1 at the moment of the call.
EASY

A dynamic array has size 4 and capacity 8. Without running any code, predict which of these calls throw an ArrayIndexOutOfBoundsException: get(3), get(4), set(-1, 5), and remove(4). Explain your reasoning using the valid range.

Hints
  • Valid positions for size 4 are 0 through 3.
  • A negative index is always rejected.
  • Capacity does not affect the check.

Where ArrayList Fits In

Writing a separate class for every element type would be tedious. A DynamicArrayOfString would be needed for strings, a DynamicArrayOfPlayer for players, and so on. Java avoids that repetition with a built-in standard class, ArrayList, which implements dynamic arrays and can hold any type of data. It follows the same design ideas developed in this article: a growable collection with operations to add, remove, get, and set items.

Key Takeaways

  • A dynamic array keeps two numbers: the capacity, which is the internal array length, and the size, which counts the items that hold meaningful values.
  • add and remove are the only operations that change the size; add doubles the capacity by copying into a larger array when the storage is full, and remove shifts later values forward without shrinking storage.
  • Valid positions run from 0 through size minus 1. Negative indices and indices at or beyond the size cause an ArrayIndexOutOfBoundsException in get, set, and remove, even when the capacity has room for them.
  • Tracing each call and recording the size and contents after every step is the reliable way to predict what a sequence of operations produces.