AP CSA Unit 4 Study Guide (2026) - Arrays, ArrayList, 2D Arrays and Sorting
AP CSA Study Guides
AP Computer Science A
Ultimate Study Guide - 2025-2026 Curriculum
Unit 4 Overview
Unit 4: Data Collections is the largest and most important unit on the AP CSA Exam. It covers everything from basic arrays to complex algorithms, and accounts for nearly 40% of your exam score.
Plus TWO of the four Free Response Questions:
- FRQ #3: Data Analysis with ArrayList
- FRQ #4: 2D Array — nested loops, row/column processing
Unit 4 content appears in roughly 50% of your total exam score!
What's New for 2025-2026
The College Board added reading from text files using the File and Scanner classes. You'll need to know: hasNext(), nextLine(), nextInt(), next(), the throws IOException clause, and processing file data into arrays or ArrayLists.
Unit 4 Topic Breakdown
| Topic | Content | Exam Focus |
|---|---|---|
| 4.1 | Ethical & Social Issues Around Data Collection | Conceptual MCQ |
| 4.2 | Introduction to Using Data Sets | Conceptual MCQ |
| 4.3 | Array Creation and Access | MCQ, FRQ 3 & 4 |
| 4.4 | Array Traversals | MCQ, FRQ 3 & 4 |
| 4.5 | Implementing Array Algorithms | MCQ, FRQ 3 & 4 |
| 4.6 | Using Text Files NEW | MCQ, possibly FRQ |
| 4.7 | Wrapper Classes (Integer, Double) | MCQ, FRQ 3 |
| 4.8 | ArrayList Methods | Heavy MCQ, FRQ 3 |
| 4.9 | ArrayList Traversals | Heavy MCQ, FRQ 3 |
| 4.10 | Implementing ArrayList Algorithms | Heavy MCQ, FRQ 3 |
| 4.11 | 2D Array Creation and Access | Heavy MCQ, FRQ 4 |
| 4.12 | 2D Array Traversals | Heavy MCQ, FRQ 4 |
| 4.13 | Implementing 2D Array Algorithms | Heavy MCQ, FRQ 4 |
| 4.14 | Searching Algorithms | MCQ (tracing) |
| 4.15 | Sorting Algorithms | MCQ (tracing) |
| 4.16 | Recursion | MCQ (tracing) |
| 4.17 | Recursive Searching and Sorting | MCQ (tracing) |
Arrays
An array is a container that holds a fixed number of values of a single type. Once created, an array's size cannot change. Arrays are the foundation of data collections in Java.
Creating Arrays
Method 1: Declare Size (Elements Get Default Values)
// Create array of 5 integers - all initialized to 0
int[] numbers = new int[5];
// numbers = [0, 0, 0, 0, 0]
// Create array of 3 Strings - all initialized to null
String[] names = new String[3];
// names = [null, null, null]
// Create array of 4 doubles - all initialized to 0.0
double[] prices = new double[4];
Method 2: Initialize with Values (Size Inferred)
int[] scores = {85, 90, 78, 92, 88}; // length = 5
String[] colors = {"red", "green", "blue"}; // length = 3
Default Values by Type
| Element Type | Default Value | Example |
|---|---|---|
int |
0 |
new int[3] → [0, 0, 0] |
double |
0.0 |
new double[2] → [0.0, 0.0] |
boolean |
false |
new boolean[2] → [false, false] |
String / any object |
null |
new String[2] → [null, null] |
Accessing and Modifying Elements
int[] arr = {10, 20, 30, 40, 50};
// Access elements using index (0-based!)
System.out.println(arr[0]); // 10 (first element)
System.out.println(arr[2]); // 30 (third element)
System.out.println(arr[arr.length - 1]); // 50 (last element)
// Get array length (no parentheses - it's a field, not a method)
System.out.println(arr.length); // 5
// Modify elements
arr[0] = 100; // [100, 20, 30, 40, 50]
arr[4] = arr[3] + 10; // [100, 20, 30, 40, 50]
Array Index Diagram
// Index: 0 1 2 3 4
// +-----+-----+-----+-----+-----+
// arr: | 10 | 20 | 30 | 40 | 50 |
// +-----+-----+-----+-----+-----+
//
// arr.length = 5
// Valid indices: 0 through arr.length - 1 (0 through 4)
// First element: arr[0]
// Last element: arr[arr.length - 1] = arr[4]
Accessing index < 0 or ≥ length causes a runtime crash. The #1 runtime error on the AP exam. Always check your loop bounds!
int[] arr = {10, 20, 30}; // valid indices: 0, 1, 2
arr[3]; // CRASH! Index 3 doesn't exist
arr[-1]; // CRASH! Negative indices don't exist
arr[arr.length]; // CRASH! arr.length = 3, so arr[3] is out of bounds
Arrays of Objects
// Array of String objects - initialized to null, NOT empty strings!
String[] words = new String[3];
// words = [null, null, null]
words[0] = "Hello";
words[1] = "World";
// words = ["Hello", "World", null]
// Calling a method on null causes NullPointerException!
if (words[2] != null) // Always null-check first
System.out.println(words[2].length()); // Safe
- Size is FIXED once created — you cannot add or remove elements
- Use
arr.length(no parentheses) for size - Indices go from
0tolength - 1 - Object arrays initialize to
null, not empty objects - Primitive arrays (int, double, boolean) get
0,0.0,false
int[] a = {1, 2, 3};
int[] b = a; // b points to the SAME array as a (alias!)
b[0] = 99;
System.out.println(a[0]); // 99 — a was also changed!
// To make an independent copy, loop and copy each element:
int[] copy = new int[a.length];
for (int i = 0; i < a.length; i++)
copy[i] = a[i];
Array Traversals
Traversing an array means visiting each element to read, process, or modify its value. There are five traversal patterns you need to know for the AP exam.
Pattern 1: Standard for Loop
Use when you need the index, need to modify elements, or need to compare adjacent elements.
int[] arr = {10, 20, 30, 40, 50};
for (int i = 0; i < arr.length; i++)
System.out.println("Index " + i + ": " + arr[i]);
// Index 0: 10 | Index 1: 20 | ... | Index 4: 50
for (int i = 0; i < arr.length; i++) // ✔ Correct: 0 to length-1
for (int i = 0; i <= arr.length; i++) // ✖ Crash: tries arr[length]
Pattern 2: Enhanced for Loop (for-each)
Use when you only need to read values and don't need the index. Cleaner syntax, but cannot modify.
for (int num : arr)
System.out.println(num);
// Output: 10, 20, 30, 40, 50 (each on new line)
// Works with String arrays too:
String[] words = {"apple", "banana", "cherry"};
for (String word : words)
System.out.println(word.toUpperCase());
// APPLE BANANA CHERRY
int[] arr = {10, 20, 30};
for (int num : arr)
num = num * 2; // Only changes local copy — array UNCHANGED
// arr is still [10, 20, 30]
// To modify, use a standard for loop:
for (int i = 0; i < arr.length; i++)
arr[i] = arr[i] * 2; // This DOES modify: [20, 40, 60]
Pattern 3: Backward Traversal
Essential for removing elements from ArrayList while traversing.
for (int i = arr.length - 1; i >= 0; i--)
System.out.println(arr[i]);
// Output: 50, 40, 30, 20, 10
Pattern 4: Partial Traversal
int[] arr = {10, 20, 30, 40, 50, 60};
// First half only
for (int i = 0; i < arr.length / 2; i++)
System.out.print(arr[i] + " "); // 10 20 30
// Every other element (even indices)
for (int i = 0; i < arr.length; i += 2)
System.out.print(arr[i] + " "); // 10 30 50
// Every other element (odd indices)
for (int i = 1; i < arr.length; i += 2)
System.out.print(arr[i] + " "); // 20 40 60
Pattern 5: Accessing Adjacent Elements
Used to compare neighbors — requires careful bounds to avoid crashing.
int[] arr = {10, 20, 30, 40, 50};
// Compare each element with the NEXT one (stop at length - 1!)
for (int i = 0; i < arr.length - 1; i++)
{
if (arr[i] > arr[i + 1])
System.out.println(arr[i] + " > " + arr[i + 1]);
}
// Compare each element with the PREVIOUS one (start at 1!)
for (int i = 1; i < arr.length; i++)
{
if (arr[i] < arr[i - 1])
System.out.println(arr[i] + " < " + arr[i - 1]);
}
| Situation | Use |
|---|---|
| Just reading values | Enhanced for loop |
| Need the index | Standard for loop |
| Modifying elements | Standard for loop |
| Comparing adjacent elements | Standard for loop |
| Traversing backward | Standard for loop |
| Skipping elements (every other) | Standard for loop |
Array Algorithms
These are the standard algorithms the AP exam tests most heavily. Memorize every pattern — they appear in both MCQ tracing and FRQ writing.
Algorithm 1: Sum of Elements
int[] arr = {10, 20, 30, 40, 50};
int sum = 0; // accumulator starts at 0
for (int num : arr)
sum += num;
System.out.println(sum); // 150
Algorithm 2: Average of Elements
int sum = 0;
for (int num : arr) sum += num;
// MUST cast to double before dividing - otherwise integer division!
double average = (double) sum / arr.length;
// Without cast: 150 / 5 = 30 (truncates)
// With cast: 150.0 / 5 = 30.0 (correct)
Algorithm 3: Find Maximum
int[] arr = {23, 45, 12, 67, 34};
// Initialize to FIRST element, never to 0
int max = arr[0];
for (int i = 1; i < arr.length; i++) // start at 1
if (arr[i] > max) max = arr[i];
System.out.println(max); // 67
int[] arr = {-5, -3, -8, -1};
int max = 0; // WRONG: 0 isn't even in the array!
int max = arr[0]; // ✔ Correct: -5, updates to -1
Algorithm 4: Find Minimum
int min = arr[0];
for (int i = 1; i < arr.length; i++)
if (arr[i] < min) min = arr[i]; // just flip > to <
Algorithm 5: Count Occurrences
int[] arr = {5, 3, 5, 7, 5, 2};
int target = 5;
int count = 0;
for (int num : arr)
if (num == target) count++;
System.out.println(count); // 3
Algorithm 6: Count Elements Meeting a Condition
int[] arr = {12, 7, 25, 3, 18, 9};
int evenCount = 0;
for (int num : arr)
if (num % 2 == 0) evenCount++; // 2 (12 and 18)
int bigCount = 0;
for (int num : arr)
if (num > 10) bigCount++; // 3 (12, 25, 18)
Algorithm 7: Check if ANY Element Meets a Condition
// Start false, flip to true if you find one match
boolean hasNegative = false;
for (int num : arr)
if (num < 0) hasNegative = true;
Algorithm 8: Check if ALL Elements Meet a Condition
// Start true, flip to false if any element fails
boolean allPositive = true;
for (int num : arr)
if (num <= 0) allPositive = false;
Algorithm 9: Shift Elements Left
int[] arr = {1, 2, 3, 4, 5};
// Traverse FORWARD: each element gets the value of the next one
for (int i = 0; i < arr.length - 1; i++)
arr[i] = arr[i + 1];
arr[arr.length - 1] = 0; // clear last position
// arr = [2, 3, 4, 5, 0]
Algorithm 10: Shift Elements Right
int[] arr = {1, 2, 3, 4, 5};
// Traverse BACKWARD: each element gets the value of the previous one
for (int i = arr.length - 1; i > 0; i--)
arr[i] = arr[i - 1];
arr[0] = 0; // clear first position
// arr = [0, 1, 2, 3, 4]
Shifting left? Traverse forward (overwrite each element with its right neighbor). Shifting right? Traverse backward (overwrite each element with its left neighbor). If you go the wrong direction you clobber data before you use it.
Algorithm 11: Reverse an Array
int[] arr = {1, 2, 3, 4, 5};
// Swap elements from outside in, only go halfway
for (int i = 0; i < arr.length / 2; i++)
{
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
// Trace: swap arr[0]&arr[4] → [5,2,3,4,1]
// swap arr[1]&arr[3] → [5,4,3,2,1]
// i=2 is NOT < 5/2=2, loop ends
// arr = [5, 4, 3, 2, 1]
Algorithm 12: Copy an Array
int[] original = {1, 2, 3, 4, 5};
int[] copy = new int[original.length];
for (int i = 0; i < original.length; i++)
copy[i] = original[i];
copy[0] = 99;
// copy = [99, 2, 3, 4, 5]
// original = [1, 2, 3, 4, 5] (unchanged)
Using Text Files NEW for 2025-2026
The College Board added file I/O to the curriculum. Use the File and Scanner classes to read data from text files.
Basic File Reading Pattern
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public static void readData() throws IOException
{
File myFile = new File("data.txt");
Scanner input = new Scanner(myFile);
while (input.hasNext())
{
String line = input.nextLine();
System.out.println(line);
}
input.close();
}
Any method that reads from a file must include throws IOException in its header.
Scanner Methods for Files
| Method | Returns | Description |
|---|---|---|
hasNext() |
boolean |
True if more data to read |
nextLine() |
String |
Reads entire next line |
next() |
String |
Reads next word |
nextInt() |
int |
Reads next integer |
nextDouble() |
double |
Reads next double |
Reading Numbers into an ArrayList
public static ArrayList<Integer> loadNumbers(String filename) throws IOException
{
ArrayList<Integer> numbers = new ArrayList<Integer>();
Scanner input = new Scanner(new File(filename));
while (input.hasNext())
numbers.add(input.nextInt()); // autoboxing: int → Integer
input.close();
return numbers;
}
Wrapper Classes
ArrayLists can only hold objects, not primitive types. Wrapper classes wrap each primitive in an object so it can be stored in an ArrayList.
Primitive Types and Their Wrappers
| Primitive | Wrapper Class | Create Manually |
|---|---|---|
int |
Integer |
Integer.valueOf(5) |
double |
Double |
Double.valueOf(3.14) |
boolean |
Boolean |
Boolean.valueOf(true) |
Autoboxing: Automatic Primitive → Object
// Java automatically converts primitives to wrapper objects
Integer num = 42; // int 42 is "boxed" into an Integer object
Double price = 9.99; // double is boxed into Double
// Autoboxing happens automatically when adding to ArrayList
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(10); // int 10 autoboxed to Integer
numbers.add(20);
numbers.add(30);
Unboxing: Automatic Object → Primitive
Integer numObj = 42;
int numPrim = numObj; // Integer is automatically "unboxed" to int
// Unboxing happens automatically when getting from ArrayList
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(100);
int value = numbers.get(0); // Integer unboxed to int
int doubled = numbers.get(0) * 2; // unboxing happens automatically
Integer num = null;
int x = num + 5; // CRASH! Can't unbox null
// Can also happen with ArrayList:
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(null); // legal - ArrayList can store null
int val = list.get(0); // CRASH! Unboxing null
Useful Integer Methods
// Convert String to int
int num = Integer.parseInt("123"); // 123
// Maximum and minimum int values
int maxInt = Integer.MAX_VALUE; // 2147483647
int minInt = Integer.MIN_VALUE; // -2147483648
// Convert String to double
double pi = Double.parseDouble("3.14159"); // 3.14159
- Declaring an
ArrayListorArrayList - Storing primitives as
Objectreferences - Using
Integer.parseInt()orDouble.parseDouble() - In practice, autoboxing handles the conversion for you — just know it's happening
ArrayList
An ArrayList is a resizable array. Unlike a fixed array, you can add and remove elements at any time. It is the most-tested data structure on FRQ #3.
Creating an ArrayList
import java.util.ArrayList;
// Declare and initialize (empty)
ArrayList<String> names = new ArrayList<String>();
ArrayList<Integer> nums = new ArrayList<Integer>();
All ArrayList Methods
| Method | What It Does | Returns |
|---|---|---|
add(obj) |
Appends obj to the end |
boolean (always true) |
add(index, obj) |
Inserts obj at index; shifts others right | void |
get(index) |
Returns element at index |
E (element type) |
set(index, obj) |
Replaces element at index | old element |
remove(index) |
Removes and returns element; shifts others left | removed element |
size() |
Returns number of elements | int |
Complete Traced Example
ArrayList<String> list = new ArrayList<String>();
list.add("A"); // [A] size=1
list.add("B"); // [A, B] size=2
list.add("C"); // [A, B, C] size=3
list.add(1, "X"); // [A, X, B, C] size=4 (B, C shift right)
list.set(0, "Z"); // [Z, X, B, C] size=4 (A replaced)
list.remove(2); // [Z, X, C] size=3 (B removed, C shifts left)
System.out.println(list.get(1)); // X
System.out.println(list.size()); // 3
Traversing an ArrayList
ArrayList<Integer> nums = new ArrayList<Integer>();
nums.add(10); nums.add(20); nums.add(30);
// Standard for loop (use when you need the index)
for (int i = 0; i < nums.size(); i++)
System.out.println(nums.get(i));
// Enhanced for loop (cleaner, use when you only need values)
for (int num : nums)
System.out.println(num);
| Property | Array | ArrayList |
|---|---|---|
| Size | Fixed | Dynamic (grows/shrinks) |
| Element types | Primitives or objects | Objects only (use wrapper types) |
| Size syntax | arr.length |
list.size() |
| Access syntax | arr[i] |
list.get(i) |
| Modify syntax | arr[i] = x |
list.set(i, x) |
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
// WRONG: == compares references, not content
if (list.get(0) == "hello") ... // may be false!
// CORRECT: .equals() compares string content
if (list.get(0).equals("hello")) ... // ✔ always correct
ArrayList Algorithms
ArrayList algorithms are heavily tested on FRQ #3. The most important distinction from array algorithms is how you handle removal — getting this wrong is the most common FRQ mistake.
ArrayList<Integer> list = new ... // [1, 2, 3, 4]
// WRONG — when element is removed, next element shifts left
// and gets skipped by i++ :
for (int i = 0; i < list.size(); i++)
if (list.get(i) % 2 == 0) list.remove(i);
// Result: [1, 3] -- wrong! Should be [1, 3] but arrives there wrong
// Worse: if two consecutive evens [2, 4], the 4 gets skipped entirely
// CORRECT — traverse backward so shifts don't affect unvisited indices
for (int i = list.size() - 1; i >= 0; i--)
if (list.get(i) % 2 == 0) list.remove(i);
Algorithm 1: Remove All Occurrences of a Value
public static void removeAll(ArrayList<String> list, String target)
{
for (int i = list.size() - 1; i >= 0; i--)
if (list.get(i).equals(target))
list.remove(i);
}
Algorithm 2: Filter into a New List
When you want to keep elements that meet a condition, build a new list rather than removing from the original.
public static ArrayList<Integer> getEvens(ArrayList<Integer> nums)
{
ArrayList<Integer> result = new ArrayList<Integer>();
for (int n : nums)
if (n % 2 == 0) result.add(n);
return result;
}
Algorithm 3: Find Maximum in an ArrayList
public static int findMax(ArrayList<Integer> nums)
{
int max = nums.get(0); // initialize to first element
for (int n : nums)
if (n > max) max = n;
return max;
}
Algorithm 4: Count Elements Meeting a Condition
public static int countAbove(ArrayList<Integer> nums, int threshold)
{
int count = 0;
for (int n : nums)
if (n > threshold) count++;
return count;
}
Algorithm 5: Sum of All Elements
public static int sumList(ArrayList<Integer> nums)
{
int sum = 0;
for (int n : nums) sum += n;
return sum;
}
Algorithm 6: Find and Update In-Place
// Double every element greater than 10
for (int i = 0; i < list.size(); i++)
if (list.get(i) > 10)
list.set(i, list.get(i) * 2);
- Use
list.size()notlist.length(no such thing) - Use
list.get(i)notlist[i](not valid syntax) - Use
list.set(i, val)to replace, notlist[i] = val - When removing elements, always traverse backward
- Use
.equals()to compare String elements, never==
2D Arrays
A 2D array is an array of arrays — think of it as a grid or table with rows and columns. FRQ #4 is always a 2D array problem.
Creating 2D Arrays
// Method 1: Declare size (all elements get default value 0)
int[][] grid = new int[3][4]; // 3 rows, 4 columns
// Method 2: Initialize with values
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 3 rows, 3 columns
Accessing Elements and Dimensions
// Access: [row][col] — row first, ALWAYS
System.out.println(matrix[0][0]); // 1 (row 0, col 0)
System.out.println(matrix[1][2]); // 6 (row 1, col 2)
System.out.println(matrix[2][1]); // 8 (row 2, col 1)
// Get dimensions
System.out.println(matrix.length); // 3 (number of rows)
System.out.println(matrix[0].length); // 3 (number of columns)
// Modify elements
matrix[1][1] = 99; // row 1, col 1 changed to 99
2D Array Diagram
// col 0 col 1 col 2
// row 0: [ 1 ] [ 2 ] [ 3 ] matrix[0][0]=1 matrix[0][2]=3
// row 1: [ 4 ] [ 5 ] [ 6 ] matrix[1][1]=5 matrix[1][2]=6
// row 2: [ 7 ] [ 8 ] [ 9 ] matrix[2][0]=7 matrix[2][2]=9
matrix[1][2] // Row 1, Col 2 → 6 ✔
matrix[2][1] // Row 2, Col 1 → 8 (different element!)
matrix.length // Number of ROWS = 3
matrix[0].length // Number of COLUMNS = 3
Row-Major Traversal (most common)
Outer loop = rows, inner loop = columns. Visits elements left to right, row by row.
for (int r = 0; r < matrix.length; r++)
{
for (int c = 0; c < matrix[0].length; c++)
{
System.out.print(matrix[r][c] + " ");
}
System.out.println(); // newline after each row
}
// Output:
// 1 2 3
// 4 5 6
// 7 8 9
Column-Major Traversal
Outer loop = columns, inner loop = rows. Visits elements top to bottom, column by column.
for (int c = 0; c < matrix[0].length; c++)
{
for (int r = 0; r < matrix.length; r++)
System.out.print(matrix[r][c] + " ");
}
// Output: 1 4 7 2 5 8 3 6 9
Enhanced for Loop with 2D Arrays
// "for each row, for each element in that row"
for (int[] row : matrix)
for (int val : row)
System.out.print(val + " ");
- Row indices:
0toarr.length - 1 - Column indices:
0toarr[0].length - 1 - Always use
arr[0].lengthfor columns (notarr.length) - For a non-square grid: rows and columns may differ
2D Array Algorithms
2D array algorithms always use nested loops — outer for rows, inner for columns (row-major). FRQ #4 is always a 2D array problem.
Algorithm 1: Sum All Elements
public static int sumAll(int[][] arr)
{
int sum = 0;
for (int r = 0; r < arr.length; r++)
for (int c = 0; c < arr[0].length; c++)
sum += arr[r][c];
return sum;
}
Algorithm 2: Count Elements Meeting a Condition
public static int countPositive(int[][] arr)
{
int count = 0;
for (int r = 0; r < arr.length; r++)
for (int c = 0; c < arr[0].length; c++)
if (arr[r][c] > 0) count++;
return count;
}
Algorithm 3: Sum a Single Row
public static int rowSum(int[][] arr, int row)
{
int sum = 0;
for (int c = 0; c < arr[0].length; c++)
sum += arr[row][c];
return sum;
}
Algorithm 4: Sum a Single Column
public static int colSum(int[][] arr, int col)
{
int sum = 0;
for (int r = 0; r < arr.length; r++)
sum += arr[r][col];
return sum;
}
Algorithm 5: Find Maximum in a 2D Array
public static int findMax(int[][] arr)
{
int max = arr[0][0]; // initialize to first element
for (int r = 0; r < arr.length; r++)
for (int c = 0; c < arr[0].length; c++)
if (arr[r][c] > max) max = arr[r][c];
return max;
}
Algorithm 6: Check if Any Row Contains a Value
public static boolean rowContains(int[][] arr, int row, int val)
{
for (int c = 0; c < arr[0].length; c++)
if (arr[row][c] == val) return true;
return false;
}
Algorithm 7: Copy All Elements to a New 2D Array
public static int[][] copy2D(int[][] arr)
{
int[][] result = new int[arr.length][arr[0].length];
for (int r = 0; r < arr.length; r++)
for (int c = 0; c < arr[0].length; c++)
result[r][c] = arr[r][c];
return result;
}
- Outer loop = rows using
arr.length - Inner loop = columns using
arr[0].length - Access with
arr[r][c]— row first, always - For a single row: one loop over columns, row index is fixed
- For a single column: one loop over rows, column index is fixed
4.14 — Searching Algorithms
The AP exam tests two searching algorithms exclusively: linear search and binary search. You must be able to trace both, identify which is appropriate for a given scenario, and spot errors in their implementations.
Linear Search
Linear search checks every element one at a time from left to right. It works on any array — sorted or unsorted.
// Returns index of target, or -1 if not found public static int linearSearch(int[] arr, int target) { for (int k = 0; k < arr.length; k++) { if (arr[k] == target) { return k; // return index, not the value } } return -1; // -1 signals "not found" }
Step-by-Step Trace
Array: {14, 7, 31, 9, 22} Target: 9
| k | arr[k] | arr[k] == 9? | Action |
|---|---|---|---|
| 0 | 14 | No | continue |
| 1 | 7 | No | continue |
| 2 | 31 | No | continue |
| 3 | 9 | YES | return 3 |
Binary Search
Binary search repeatedly cuts the search range in half. It is dramatically faster than linear search — but it requires a sorted array.
// Iterative binary search — returns index of target, or -1 // PRECONDITION: arr is sorted in ascending order public static int binarySearch(int[] arr, int target) { int lo = 0; int hi = arr.length - 1; while (lo <= hi) { int mid = (lo + hi) / 2; if (arr[mid] == target) { return mid; } else if (arr[mid] < target) { lo = mid + 1; // target is in right half } else { hi = mid - 1; // target is in left half } } return -1; }
Step-by-Step Trace
Sorted array: {3, 8, 12, 19, 25, 34, 47} Target: 19 Indices: 0–6
| lo | hi | mid | arr[mid] | Comparison | Action |
|---|---|---|---|---|---|
| 0 | 6 | 3 | 19 | 19 == 19 | return 3 |
Now trace a miss. Target: 20
| lo | hi | mid | arr[mid] | Comparison | Action |
|---|---|---|---|---|---|
| 0 | 6 | 3 | 19 | 19 < 20 | lo = 4 |
| 4 | 6 | 5 | 34 | 34 > 20 | hi = 4 |
| 4 | 4 | 4 | 25 | 25 > 20 | hi = 3 |
| 4 | 3 | — | — | lo > hi | return -1 |
- Works on sorted OR unsorted arrays
- Checks every element in worst case
- Worst case: target not present (checks all n)
- Best case: target is at index 0
- Requires sorted array
- Eliminates half the range each step
- For n=1000: at most ~10 comparisons
- For n=1,000,000: at most ~20 comparisons
target in arr, or -1 if not found. The method does NOT always work as intended.
public static int locate(int[] arr, int target) { for (int k = 0; k <= arr.length; k++) { // Line 2 if (arr[k] == target) { return k; } } return -1; }
0 instead of -1 when the target is not foundArrayIndexOutOfBoundsException when the target is not in arr
k = 1 to avoid checking the default value at index 0k <= arr.length allows k to reach arr.length, which is one past the last valid index. When the target is absent, the loop reaches arr[arr.length] and throws ArrayIndexOutOfBoundsException. The fix is k < arr.length. The other options describe behaviors that are either correct or irrelevant to the actual bug.
public static int bFind(int[] arr, int val, int lo, int hi) { if (lo > hi) return -1; int mid = (lo + hi) / 2; if (arr[mid] == val) return mid; if (arr[mid] < val) return bFind(arr, val, lo, mid - 1); // Line 6 return bFind(arr, val, mid + 1, hi); }
arr is always passed in sorted ascending order. Which of the following statements about this method are true?I. Line 6 contains a logic error: when
arr[mid] < val, the search should move to the right half, not the left.II. If
val equals arr[0], the method always returns 0.III. When called on a one-element array where
val is not present, the method correctly returns -1.
Statement I — TRUE: When
arr[mid] < val, the target must be in the right half (mid+1 to hi). Line 6 incorrectly searches the left half (lo to mid-1), so most searches will fail.Statement II — FALSE: Because of the bug on line 6, searching for a value that is larger than
arr[0] will recursively narrow to the wrong half. Only if val == arr[mid] on the first call (i.e., val happens to be the middle element) will it be found at all. There is no guarantee the method reaches index 0.Statement III — TRUE: On a one-element array,
lo == hi == 0. mid = 0. If arr[0] != val, neither recursive branch matches exactly, and lo > hi is reached on the next call, returning -1 correctly regardless of the bug on line 6.
4.15 — Sorting Algorithms
The AP exam tests selection sort and insertion sort exclusively. You must trace them pass by pass. You will never be asked to write them from scratch on the MCQ — but you must read them fluently and predict what the array looks like after k passes.
Selection Sort
On each pass, selection sort finds the minimum in the unsorted portion and swaps it into position. After k passes, the first k elements are in their final sorted positions.
public static void selectionSort(int[] data) { for (int j = 0; j < data.length - 1; j++) { int minIdx = j; for (int k = j + 1; k < data.length; k++) { if (data[k] < data[minIdx]) { minIdx = k; } } int temp = data[j]; data[j] = data[minIdx]; data[minIdx] = temp; } }
Pass-by-Pass Trace
Starting array: {42, 17, 35, 8, 26}
data.length - 1 times (not data.length times — the last element falls into place automatically).
Insertion Sort
Insertion sort builds a sorted sub-array on the left. On each pass, it picks the next element and inserts it into the correct position by shifting larger elements rightward.
public static void insertionSort(int[] data) { for (int j = 1; j < data.length; j++) { int temp = data[j]; int pos = j - 1; while (pos >= 0 && data[pos] > temp) { data[pos + 1] = data[pos]; pos--; } data[pos + 1] = temp; } }
Pass-by-Pass Trace
Starting array: {42, 17, 35, 8, 26}
Key Differences: Selection vs. Insertion
| Property | Selection Sort | Insertion Sort |
|---|---|---|
| Strategy | Find min, swap into position | Take next element, shift and insert |
| Outer loop starts at | j = 0 | j = 1 |
| Swaps per pass | Exactly 1 swap | 0 to j shifts |
| After k passes | First k elements are final | First k+1 elements are sorted (but not necessarily final) |
| Best case input | O(n²) regardless | O(n) on already-sorted array |
public static void sSort(int[] d) { for (int j = 0; j < d.length - 1; j++) { int m = j; for (int k = j + 1; k < d.length; k++) { if (d[k] < d[m]) m = k; } int t = d[j]; d[j] = d[m]; d[m] = t; } }
sSort is called with the array {53, 21, 7, 38, 14}. What does the array contain after the outer loop has completed exactly two iterations (after j == 1 finishes)?
Pass 1 (j=0): The minimum of the full array is 7 (index 2). Swap d[0] and d[2]:
{7, 21, 53, 38, 14}.Pass 2 (j=1): The minimum from index 1 onward is 14 (index 4). Swap d[1] and d[4]:
{7, 14, 53, 38, 21}.After two passes, only the first two positions (7 and 14) are in their final sorted places. The rest of the array is in an intermediate state — do not assume it is sorted beyond the first j positions.
public static void iSort(int[] d) { for (int j = 1; j < d.length; j++) { int tmp = d[j]; int p = j - 1; while (p >= 0 && d[p] > tmp) { d[p + 1] = d[p]; p--; } d[p] = tmp; // Line 8 } }
j = 0, not j = 1
d[p + 1] = tmp; assigning to d[p] overwrites an element that was not shiftedwhile condition should use d[p] >= tmp to handle duplicate valuestmp should be assigned d[j - 1] rather than d[j]
p points to the last element that was not shifted (i.e., the element at d[p] is already ≤ tmp). The correct insertion point is p + 1. Writing d[p] = tmp overwrites a valid sorted element. This is one of the most common off-by-one errors in insertion sort implementations.
4.16 / 4.17 — Recursion & Recursive Searching/Sorting
Anatomy of a Recursive Method
Every correct recursive method has exactly two components:
1. Base Case
The condition that stops the recursion. Without a base case, you get infinite recursion and a StackOverflowError. The base case returns a value directly — no recursive call is made.
2. Recursive Case
The method calls itself with a smaller or simpler version of the problem. Each recursive call must move closer to the base case.
// Classic example: sum of digits public static int digitSum(int n) { if (n < 10) // BASE CASE: single digit return n; return (n % 10) + digitSum(n / 10); // RECURSIVE CASE }
How to Trace a Recursive Method
Use the call stack model. Each call is pushed onto the stack. Once the base case is reached, calls start returning and the stack unwinds.
Trace: digitSum(347)
Phase 1 — Building the call stack:
Phase 2 — Unwinding:
Common Recursive Patterns on the AP Exam
| Pattern | What it does | Trace clue |
|---|---|---|
mystery(n/10) |
Processes digits right-to-left | Integer division strips the last digit |
mystery(n-1) |
Countdown / factorial-style | Counts down to 0 or 1 |
mystery(n-2) |
Fibonacci-style | Two recursive calls, not one |
String slicing: mystery(s.substring(1))
|
Processes string character by character | Each call removes the first character |
mystery(arr, index+1) |
Array traversal via recursion | Index walks from 0 to arr.length |
Recursive Binary Search (4.17)
/** Precondition: arr is sorted ascending. * Returns index of val, or -1 if not found. */ public static int rBinSearch(int[] arr, int val, int lo, int hi) { if (lo > hi) // BASE CASE: range exhausted return -1; int mid = (lo + hi) / 2; if (arr[mid] == val) // BASE CASE: found it return mid; if (arr[mid] < val) return rBinSearch(arr, val, mid + 1, hi); // search right return rBinSearch(arr, val, lo, mid - 1); // search left }
This is identical in logic to iterative binary search — the only difference is that the loop is replaced with recursive calls. The call stack builds up to O(log n) levels deep.
public static int proc(int n) { if (n <= 0) return 5; return n + proc(n - 2); }
proc(6)?Trace the call chain step by step:
proc(6) = 6 + proc(4)proc(4) = 4 + proc(2)proc(2) = 2 + proc(0)proc(0) = 5 ← base case (n ≤ 0)Unwinding:
proc(2) = 2+5 = 7 → proc(4) = 4+7 = 11 → proc(6) = 6+11 = 20Common mistake: Students often stop at
proc(-2) or add an extra call. The base case is n <= 0, which fires at n = 0. The step is n-2, so 6 → 4 → 2 → 0 → base case. That’s 4 active calls, not 3.
public static void printIt(String s) { if (s.length() == 0) return; System.out.print(s.substring(s.length() - 1)); printIt(s.substring(0, s.length() - 1)); }
printIt("JAVA") are true?I. The method prints the characters of
"JAVA" in reverse order: AVAJ.II. The base case is reached after exactly 4 recursive calls.
III. The output would be identical if the two statements in the method body were swapped (print first, then recurse, vs. recurse first, then print).
Statement I — TRUE: Each call prints the last character of the current string, then recurses on everything except the last character. So the call sequence prints: A, V, A, J → output is
AVAJ. Reversed. ✓Statement II — FALSE: The base case fires when
s.length() == 0. Starting with "JAVA" (length 4): "JAV", "JA", "J", "" — that is 4 recursive calls before the base case, but the base case itself is not a recursive call — it is the 5th activation frame. So the base case is reached after 4 recursive calls have been made, but the total number of method activations is 5. This statement is ambiguous enough to be false as written.Statement III — FALSE: If you swap the statements (recurse first, then print), the recursion builds the full call stack first, and printing happens on the way back up — which would print the characters in forward order (J, A, V, A), the opposite of the current behavior.
⚠ Exam Traps: Search, Sort & Recursion
k < arr.length vs k <= arr.length and k <= arr.length - 1 are the most-tested bugs in searching/sorting code.StackOverflowError. A base case that fires too early returns a wrong value silently — know the difference.(arr, val, lo, mid-1) instead of (arr, val, mid+1, hi) when the target is in the right half (and vice versa).Common Exam Traps
1. Off-by-One Loop Bounds
✗ Wrong
i <= arr.length
✔ Correct
i < arr.length
2. length vs length() vs size()
| Array | arr.length |
| String | str.length() |
| ArrayList | list.size() |
3. 2D Array [row][col]
✗ Wrong
grid[col][row]
✔ Correct
grid[row][col]
4. String Comparison
✗ Wrong
str1 == str2
✔ Correct
str1.equals(str2)
5. Enhanced for Can't Modify
for (int num : arr)
num = num * 2; // Only changes local copy — array unchanged!
6. Initialize max to arr[0], not 0
int max = 0; // Fails for all-negative arrays
int max = arr[0]; // ✔ Correct
7. Binary Search Needs Sorted Data
int[] unsorted = {50, 10, 30};
binarySearch(unsorted, 30); // Unpredictable result!
Practice Questions
Q1: Array Traversal
int[] arr = {2, 4, 6, 8, 10};
int sum = 0;
for (int i = 0; i < arr.length; i += 2)
sum += arr[i];
System.out.println(sum);
Indices 0,2,4: arr[0]+arr[2]+arr[4] = 2+6+10 = 18
Q2: Enhanced For Loop
int[] arr = {1, 2, 3};
for (int num : arr)
num = num + 10;
System.out.println(arr[1]);
Enhanced for loop doesn't modify the array —
num is a local copy.Q3: 2D Array Access
int[][] grid = {{1,2,3}, {4,5,6}, {7,8,9}};
System.out.println(grid[2][0] + grid[0][2]);
grid[2][0]=7, grid[0][2]=3, sum=10
Q4: ArrayList Operations
ArrayList<String> list = new ArrayList<String>();
list.add("A"); list.add("B"); list.add("C");
list.add(1, "X");
list.remove(2);
System.out.println(list);
After adds: [A,B,C]. Insert X at 1: [A,X,B,C]. Remove idx 2 (B): [A,X,C]
FRQ Practice
FRQ 1: ArrayList Processing (FRQ #3 Style)
Part A: Write countAbove returning the count of scores above a threshold.
public static int countAbove(ArrayList<Integer> scores, int threshold)
Part B: Write removeBelow removing all scores below a passing grade.
public static void removeBelow(ArrayList<Integer> scores, int passing)
public static int countAbove(ArrayList<Integer> scores, int threshold)
{
int count = 0;
for (int score : scores)
if (score > threshold) count++;
return count;
}
Part B:
public static void removeBelow(ArrayList<Integer> scores, int passing)
{
for (int i = scores.size() - 1; i >= 0; i--)
if (scores.get(i) < passing)
scores.remove(i);
}
Rubric (Part B — 5 pts)
+1 Traverses backward
+1 Uses get(i) correctly
+1 Compares to passing
+1 Uses remove(i)
+1 No elements skipped
FRQ 2: 2D Array Processing (FRQ #4 Style)
Write countEmpty returning how many null elements are in a 2D String array.
public static int countEmpty(String[][] chart)
public static int countEmpty(String[][] chart)
{
int count = 0;
for (int r = 0; r < chart.length; r++)
for (int c = 0; c < chart[0].length; c++)
if (chart[r][c] == null) count++;
return count;
}
FRQ 3: File I/O (NEW Topic!)
Write loadData that reads integers from a file into an ArrayList.
public static ArrayList<Integer> loadData(String filename) throws IOException
public static ArrayList<Integer> loadData(String filename) throws IOException
{
ArrayList<Integer> data = new ArrayList<Integer>();
Scanner input = new Scanner(new File(filename));
while (input.hasNext())
data.add(input.nextInt());
input.close();
return data;
}
Quick Reference
| Structure | Create | Access | Size |
|---|---|---|---|
| Array | new int[5] |
arr[0] |
arr.length |
| ArrayList | new ArrayList |
list.get(0) |
list.size() |
| 2D Array | new int[3][4] |
grid[r][c] |
grid.length / grid[0].length
|
Algorithm Complexity
| Algorithm | Best | Worst | Requirement |
|---|---|---|---|
| Linear Search | O(1) | O(n) | None |
| Binary Search | O(1) | O(log n) | Sorted array! |
| Selection Sort | O(n²) | O(n²) | — |
| Insertion Sort | O(n) | O(n²) | Best on nearly-sorted |
Final Checklist
-
<not<=for loop bounds -
.length/.length()/.size() -
grid[row][col]notgrid[col][row] - Remove from ArrayList BACKWARD
-
.equals()for Strings, not == - Binary search requires a SORTED array
- Initialize max/min to
arr[0], not 0 - Enhanced for loop cannot modify array elements
-
throws IOExceptionrequired for file reading
Finished Unit 4? Here is What to Do Next.
Take the Unit 4 Practice Exam
Test yourself on everything you just studied. Instant scoring and explanations.
💻Write Real Code (Interactive)
FRQ-style practice with a live Java editor and auto-grading.
📝Practice Real FRQs
Every AP CSA FRQ from 2004–2025 with full solutions and scoring guides.
⚙Build a Custom Test
586 questions. Filter by unit and topic. Create your own practice exam.
Need a Structured Study Plan?
The 4-Week Cram Kit gives you a day-by-day plan covering every unit — with daily drills and FRQ practice.
Get the Cram Kit — $29.99 Book 1-on-1 TutoringDaily Practice Questions
Free daily MCQs covering all 4 units. New question every day through May 15.
🎮21 Interactive Study Games
Jeopardy, crosswords, memory match, and more. Learn while you play.
Get in Touch
Whether you're a student, parent, or teacher — I'd love to hear from you.
Just want free AP CS resources?
Enter your email below and check the subscribe box — no message needed. Students get daily practice questions and study tips. Teachers get curriculum resources and teaching strategies.
Message Sent!
Thanks for reaching out. I'll get back to you within 24 hours.
tanner@apcsexamprep.com
Courses
AP CSA, CSP, & Cybersecurity
Response Time
Within 24 hours
Prefer email? Reach me directly at tanner@apcsexamprep.com