SECTION 1
Historical Context & Motivation
Before modern programming languages existed, early computers stored data in sequential blocks of memory. Scientists and engineers needed a way to organize related values — like a series of sensor readings or a list of student scores — so they could be processed efficiently. The concept of the array emerged as one of the oldest and most fundamental data structures in computing. An array gives each element a numbered position, called an index, making it possible to jump directly to any value without scanning through the entire collection.
1950s • Fortran Introduces Arrays
1972 • C Popularizes Zero-Based Indexing
1995 • Java Adds Bounds Checking
2004+ • Enhanced For-Loops & Collections
Despite decades of improvements, one of the most common bugs in student code — and in professional software — remains the off-by-one error. Understanding how array indices work and where their boundaries lie is essential for writing correct, reliable Java programs. This lesson gives you the tools to access and update array elements confidently while steering clear of out-of-bounds crashes.
SECTION 2
Core Principles & Definitions
An array in Java is a fixed-size container that holds elements of the same type. Every element sits at a specific position determined by its integer index. Before you can read or change any element, you need to understand a handful of foundational rules that Java enforces.
Zero-Based Indexing
0, the second at index 1, and so on. If an array has n elements, the last valid index is n − 1.Fixed Length
new int[5], its length is permanently 5. You cannot add or remove slots. The .length property tells you how many slots exist.Access with Bracket Notation
arr[i] to read the value at index i, and arr[i] = value to update it. Both operations happen in constant time.Bounds Checking at Runtime
arr.length every time you access an element. An invalid index triggers an ArrayIndexOutOfBoundsException.Default Values
0 for numeric types, false for booleans, and null for object references.KEY TAKEAWAY
SECTION 3
Visual Explanation — Array Memory Layout
The diagram below shows a Java array of five integers declared as int[] scores = {88, 92, 75, 100, 64};. Each colored box represents one slot in memory. Notice that the indices run from 0 to 4 — never reaching 5, even though the array length is 5. The red zone beyond index 4 marks the out-of-bounds region that would trigger an exception.
The key insight from this diagram is the relationship between length and the maximum valid index. Because Java counts from zero, the last valid index is always length − 1. This single fact prevents most out-of-bounds errors once you internalize it. Every time you write a loop or compute an index, ask yourself: "Is this value between 0 and length − 1, inclusive?"
SECTION 4
How Array Access Works Under the Hood
When Java allocates an array, it reserves a contiguous block of memory. The variable name (like scores) stores a reference pointing to the start of this block. Accessing scores[i] tells the JVM to jump forward by i positions from the beginning. Because every element is the same size, this calculation is instant — the JVM doesn't need to scan through earlier elements.
MEMORY ADDRESS FORMULA
baseAddress is where the array data starts in memory. i is the index you supply. elementSize is the number of bytes per element (e.g., 4 bytes for an int). This formula explains why index 0 lands exactly at the base — 0 × anything is 0.
VALID INDEX CONSTRAINT
The index i must be greater than or equal to 0 and strictly less than the array's length. Note the strict inequality on the right: the index must be less than the length, never equal to it.
Common Access Patterns
Reading an element is straightforward: int x = scores[2]; copies the value at index 2 into the variable x. Updating is equally direct: scores[2] = 80; replaces the old value with 80. Both operations complete in constant time — O(1) — regardless of the array's size. This speed is one of the main advantages arrays have over other data structures.
Watch Out: Common Off-By-One Mistakes
arr[arr.length] (one past the end) and using <= instead of < in a for-loop condition. Both of these try to reach a slot that doesn't exist.SECTION 5
Iterating Safely — Loop Patterns & Pitfalls
Most array work happens inside loops. Getting the loop boundaries right is the single most important skill for avoiding out-of-bounds exceptions. Let's compare the three main iteration styles in Java and identify where mistakes tend to hide.
<= instead of <, and starting the index at 1 instead of 0. The green rule at the bottom summarizes the safe default.The for-each loop is the safest option when you don't need the index at all, because it eliminates the possibility of an off-by-one error entirely. However, if you need to modify elements in place or need to know which position you're at, the standard for loop is essential. Just remember: start at 0, use < (not <=), and compare against arr.length.
| Loop Style | When to Use | Bounds Risk |
|---|---|---|
for (int i = 0; i < arr.length; i++) | Need the index (e.g., modifying elements, comparing neighbors) | Medium — must get condition right |
for (int val : arr) | Only reading values; index not needed | Low — no manual index |
while (i < arr.length) | Complex stopping conditions or variable step sizes | Higher — must remember to increment |
Comparison of Java array loop patterns
SECTION 6
Worked Example — Finding the Maximum Value
Let's walk through a complete example that accesses every element of an array while carefully staying within bounds. We'll find the maximum value in an array of test scores.
Find the Maximum Score
- 1
Step 1 — Declare and Initialize the Array
We start by creating an array of six integers representing test scores:int[] scores = {78, 95, 63, 88, 100, 72};. The array has a length of 6, so valid indices range from 0 to 5.scores.length = 6, valid indices: 0 through 5 - 2
Step 2 — Set an Initial Maximum
We initialize a variableint max = scores[0];to hold the first element as our starting guess. This is safe because we know the array is not empty.max = 78 (the value at index 0) - 3
Step 3 — Loop from Index 1 to length − 1
We writefor (int i = 1; i < scores.length; i++). We start at 1 (because we already used index 0) and stop before 6. Inside the loop, we comparescores[i]tomaxand updatemaxif the current score is larger.Loop runs with i = 1, 2, 3, 4, 5 — all valid - 4
Step 4 — Trace Through the Iterations
i=1: scores[1]=95 > 78 → max becomes 95. i=2: 63 < 95 → no change. i=3: 88 < 95 → no change. i=4: scores[4]=100 > 95 → max becomes 100. i=5: 72 < 100 → no change. The loop ends when i reaches 6, which is not less thanscores.length, so the conditioni < 6is false and we stop safely.max = 100 - 5
Step 5 — Print the Result
System.out.println("Highest score: " + max);outputsHighest score: 100. No out-of-bounds exception occurred because the loop respected the constraint 0 ≤ i < length.Output: Highest score: 100
SECTION 7
Common Pitfalls vs. Best Practices
Now that you know how array access works, let's compare the mistakes programmers commonly make with the best practices that prevent them. Understanding both sides will help you spot errors in your own code and in code reviews.
| Common Pitfall | Best Practice | Why It Matters |
|---|---|---|
Using i <= arr.length as the loop condition | Use i < arr.length — strict less than | The index equal to length is one past the last slot, causing an exception on the final iteration. |
Hard-coding the array size in the loop condition (e.g., i < 5) | Always use arr.length so the loop adapts if the size changes | If you later change the array size and forget to update the magic number, you get a bug or a crash. |
Accessing arr[i + 1] inside a loop that runs to arr.length − 1 | Stop the loop at arr.length − 2 when using arr[i + 1] | When i is at the last valid index, i + 1 is out of bounds. Shorten the loop by one iteration. |
| Assuming user-supplied indices are valid without checking | Validate with if (index >= 0 && index < arr.length) before using | Defensive programming prevents runtime crashes from unexpected input. |
| Forgetting that an empty array (length 0) has no valid indices at all | Check if (arr.length > 0) before accessing arr[0] | Even index 0 is out of bounds for an empty array. |
Pitfalls and their corresponding best practices
KEY TAKEAWAY
.length property is the sign itself — it tells you the limit, but driving at exactly that number already puts you over the edge. Always use strictly less than when comparing against length.SECTION 8
Arrays vs. ArrayLists and Beyond
Java arrays are powerful but limited by their fixed size. As your programs grow more complex, you'll encounter the ArrayList class, which wraps an internal array and handles resizing automatically. Understanding how plain arrays work gives you the foundation to understand every other collection in Java, because most of them use arrays under the hood.
| Feature | Array (int[]) | ArrayList<Integer> |
|---|---|---|
| Size | Fixed at creation | Grows and shrinks dynamically |
| Access syntax | arr[i] | list.get(i) |
| Update syntax | arr[i] = val | list.set(i, val) |
| Length property | arr.length | list.size() |
| Out-of-bounds exception | ArrayIndexOutOfBoundsException | IndexOutOfBoundsException |
| Can store primitives directly? | Yes | No — must use wrapper classes like Integer |
Arrays vs. ArrayLists at a glance
Notice that the same zero-based indexing and the same bounds-checking logic apply to ArrayLists. If you internalize the rule "valid indices run from 0 to size − 1" now, it will transfer directly to every list-based structure you encounter in the future, including Strings (where charAt(i) follows exactly the same constraint).
Looking Ahead
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
String[] names = new String[8];. What is the index of the first element, and what is the index of the last element? Explain why names[8] would cause an error.Show answer
names[8] would throw an ArrayIndexOutOfBoundsException because index 8 is equal to the array's length, and valid indices must be strictly less than the length. Even though 8 slots exist, they are numbered 0 to 7, not 1 to 8.Question 2: BASIC CALCULATION
int[] vals = {10, 20, 30, 40, 50};, what is the value of vals[1] + vals[vals.length - 1]? Show your reasoning step by step.Show answer
vals[1] retrieves the element at index 1, which is 20. Next, vals.length is 5, so vals.length - 1 equals 4. Therefore vals[4] retrieves 50. Adding them gives 20 + 50 = 70. The expression vals[vals.length - 1] is a standard idiom for safely accessing the last element of any array.Question 3: INTERMEDIATE
int[] data = {5, 10, 15, 20};
data[0] = data[data.length];
data[data.length] = data[0];Show answer
data[data.length] is out of bounds because the last valid index is data.length - 1 (which is 3, not 4). Both lines should use data[data.length - 1] instead. Second, even after fixing the index, the swap logic itself is broken — assigning data[0] first overwrites the original value before saving it. You need a temporary variable: int temp = data[0]; data[0] = data[data.length - 1]; data[data.length - 1] = temp;.Question 4: APPLIED
int[] attendance = {28, 30, 27, 29, 30}; (one entry per day of the week, Monday through Friday). Write a loop that computes the average daily attendance. Make sure your loop does not risk an out-of-bounds error, and explain why.Show answer
int sum = 0; for (int i = 0; i < attendance.length; i++) { sum += attendance[i]; } double avg = (double) sum / attendance.length; The loop starts at index 0 and uses the strict less-than operator with attendance.length (which is 5), so i takes values 0, 1, 2, 3, 4 — all valid indices. The sum is 28 + 30 + 27 + 29 + 30 = 144, and dividing by 5 gives an average of 28.8. Casting to double ensures we get a decimal result instead of integer division.Question 5: CRITICAL THINKING
for (int i = 0; i < arr.length; i++) { if (arr[i] == arr[i + 1]) { return true; } } Explain what goes wrong, fix the code, and then explain what additional edge case should be handled if the array could be empty or have only one element.Show answer
i reaches arr.length - 1 (the last valid index), arr[i + 1] accesses index arr.length, which is out of bounds. The fix is to change the condition to i < arr.length - 1 so the loop stops one index early, ensuring i + 1 is always valid. For the edge cases: if the array has length 0, arr.length - 1 evaluates to −1, so the loop condition 0 < -1 is false and the loop never runs — safe. If the array has length 1, the condition is 0 < 0 — also false, so the loop doesn't run. Both edge cases are handled correctly after the fix.SUMMARY
Lesson Summary
Summary
Java arrays use zero-based indexing, meaning the first element lives at index 0 and the last element at index length − 1. You access and update elements using bracket notation — arr[i] to read and arr[i] = value to write — both of which happen in constant time. Java's automatic bounds checking throws an ArrayIndexOutOfBoundsException whenever an index falls outside the range 0 ≤ i < length.
To stay safe, always use strict less-than (<) when comparing a loop variable against arr.length, prefer the for-each loop when you don't need the index, and shorten loops by one when accessing arr[i + 1]. These patterns carry over directly to ArrayLists, Strings, and 2D arrays — mastering bounds safety here sets you up for success throughout your Java journey.