Summer Camp 2026 — Enroll Now APCSA & APCSP Prep Classes Starting June Learn Python, Java & Web Design AP Cybersecurity — New Course Starting from 2026 Expert-Led · Small Batches · Hands-On Projects Contact: oggntech@gmail.com Summer Camp 2026 — Enroll Now APCSA & APCSP Prep Classes Starting June Learn Python, Java & Web Design AP Cybersecurity — New Course Starting from 2026 Expert-Led · Small Batches · Hands-On Projects Contact: oggntech@gmail.com
Study Notes

Unit 4 — Data Collections

Storing and processing collections of data: arrays, ArrayLists, 2D arrays, searching, sorting, and recursion. Each section ends with a short practice set — click "Show answer" to check yourself.

4.1

Ethical and Social Issues Around Data Collection

As programs increasingly collect, store, and process data about real people, writing code well isn't the only responsibility — how that data is gathered and used matters too.

  • Privacy: collecting more personal data than a program actually needs increases risk if that data is ever leaked or misused.
  • Bias: a data set that isn't representative of the population it's meant to describe can produce systematically unfair results, even without any "biased" code — the bias comes from what was (or wasn't) collected.
  • Security: stored data can be stolen in a breach; encrypting sensitive data and limiting who/what can access it reduces that risk.
  • Consent and transparency: people generally have the right to know what data is collected about them and how it will be used.
Why it's part of CS: a technically correct program can still cause real harm if the data behind it was collected or used carelessly — thinking about this is part of responsible software design, not a separate subject.
Practice

1. A hiring algorithm is trained only on résumés from one region of the country. What kind of problem could this introduce?

Show answer
Bias — the data set isn't representative of all applicants, so the algorithm may perform unfairly for people from other regions or backgrounds it wasn't trained on.

2. Why might a developer choose to store a user's age as "under 18 / 18 and over" instead of their exact birth date, if that's all the program actually needs?

Show answer
It minimizes the personal data collected and stored — reducing the impact if that data were ever exposed, while still meeting the program's real requirement.
4.2

Introduction to Using Data Sets

So far, each variable has held exactly one value. Many real problems involve working with a whole collection of related values — a class's worth of grades, a week of temperatures — which is awkward and inflexible to represent with separate individually-named variables.

// awkward: doesn't scale, and the count is fixed in the code itself
int grade1 = 88, grade2 = 92, grade3 = 79;

// scales to any amount of data, and the count can vary at runtime
int[] grades = {88, 92, 79};

Java offers a few structures for this, covered across this unit: arrays (fixed size, fast, simple), ArrayLists (resizable, more flexible), and 2D arrays (grids of data, like a table).

Practice

1. What's the main limitation of representing five test scores as five separate variables s1, s2, s3, s4, s5?

Show answer
You can't loop over them, and the number of scores is baked into the code — adding a sixth score means changing the code itself rather than just changing the data.

2. If you don't know in advance how many items you'll need to store, and that number might grow over time, which structure from this unit is generally the better fit — array or ArrayList?

Show answer
ArrayList — it can grow and shrink at runtime, unlike a plain array, which has a fixed size set at creation.
4.3

Array Creation and Access

An array is a fixed-size, ordered collection of values of the same type, stored under one variable name.

int[] scores = new int[5];      // 5 ints, all default to 0
scores[0] = 88;
scores[1] = 92;

int[] literal = {88, 92, 79};   // declare + fill in one step

System.out.println(literal[0]);       // 88 — indices start at 0
System.out.println(literal.length);   // 3 — length is a field, no parentheses
  • Indices run from 0 to length - 1. Accessing array[length] or any negative index throws an ArrayIndexOutOfBoundsException.
  • An array's size is fixed once created — you can't add or remove slots, only change the values already in them.
  • .length (no parentheses) gives the size — different from String's .length() method (1.15).
Practice

1. What is stored in every slot of int[] nums = new int[4]; immediately after creation?

Show answer
0 in every slot — numeric array elements default to 0 (booleans default to false, object types default to null) until explicitly assigned.

2. What happens when you run int[] a = {1,2,3}; System.out.println(a[3]);?

Show answer
It throws an ArrayIndexOutOfBoundsException — valid indices for a length-3 array are only 0, 1, and 2.
4.4

Array Traversals

A traversal visits every element of an array, usually with a loop. Java offers two common styles:

// standard for loop — gives you the index
for (int i = 0; i < scores.length; i++) {
    System.out.println("Index " + i + ": " + scores[i]);
}

// enhanced for loop ("for-each") — simpler when you don't need the index
for (int s : scores) {
    System.out.println(s);
}
Standard for loopEnhanced for loop
Gives youIndex and valueJust the value
Can modify elements?Yes, via arr[i] = ...No — the loop variable is a copy
Best forNeeding position, or modifying the arraySimply reading every value
Practice

1. Why can't you double every element of an array using an enhanced for loop like for (int s : scores) { s = s * 2; }?

Show answer
The enhanced for loop's variable s is a copy of each element, not a reference back into the array — reassigning s has no effect on scores itself.

2. Write a standard for loop that replaces every negative number in an int array vals with 0.

Show answer
for (int i = 0; i < vals.length; i++) { if (vals[i] < 0) vals[i] = 0; }
4.5

Implementing Array Algorithms

The accumulator patterns from 2.9 apply directly to arrays — most array algorithms are a traversal with one small piece of logic added inside.

int[] nums = {4, 9, 2, 7, 5};
int sum = 0, max = nums[0], countOver5 = 0;

for (int n : nums) {
    sum += n;
    if (n > max) max = n;
    if (n > 5) countOver5++;
}
double average = (double) sum / nums.length;

A slightly trickier pattern is shifting elements — e.g. removing an element from an array by shifting everything after it one slot left:

// remove the element at index "index" by shifting later elements left
for (int i = index; i < nums.length - 1; i++) {
    nums[i] = nums[i + 1];
}
Practice

1. Write a loop that finds the index of the smallest value in an int array nums.

Show answer
int minIndex = 0; for (int i = 1; i < nums.length; i++) { if (nums[i] < nums[minIndex]) minIndex = i; }

2. Why does the shifting loop above stop at nums.length - 1 rather than nums.length?

Show answer
Each step reads nums[i + 1]; if i reached nums.length - 1, that read would be nums[nums.length], which is out of bounds.
4.6

Using Text Files

Data sets often start life as a text file rather than values typed directly into code. Java's Scanner (1.4) can read from a File just as easily as from System.in.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

try {
    Scanner fileIn = new Scanner(new File("scores.txt"));
    while (fileIn.hasNextLine()) {
        String line = fileIn.nextLine();
        System.out.println(line);
    }
    fileIn.close();
} catch (FileNotFoundException e) {
    System.out.println("Could not find the file.");
}
  • Reading a file can fail (wrong path, missing file), so it's wrapped in a try/catch block that handles FileNotFoundException.
  • hasNextLine() / hasNext() check whether more data remains, so the loop stops cleanly at the end of the file instead of crashing.
  • A common pattern: read each line, parse it into the right type, and store it into an array or ArrayList as you go.
Practice

1. Why is file reading wrapped in a try/catch, when reading typed keyboard input usually isn't?

Show answer
A file might not exist at the given path, or might be inaccessible — that's a realistic failure the program must handle, unlike keyboard input which is always "there."

2. What does fileIn.hasNextLine() return once every line of the file has been read?

Show answer
false — signaling the loop to stop, since there's no more data left to read.
4.7

Wrapper Classes

Primitive types (int, double, boolean) aren't objects, but some Java features — like ArrayList — only work with objects. Wrapper classes package a primitive value inside an object.

PrimitiveWrapper class
intInteger
doubleDouble
booleanBoolean
charCharacter
Integer boxed = 5;         // autoboxing: int -> Integer, automatic
int unboxed = boxed;      // unboxing: Integer -> int, automatic

ArrayList<Integer> list = new ArrayList<>();
list.add(10);            // the int 10 is autoboxed into an Integer automatically

Java handles the conversion between a primitive and its wrapper automatically in most contexts (autoboxing and unboxing), which is why ArrayList<Integer> feels almost as easy to use as an int[] even though it stores objects.

Practice

1. Why can't you write ArrayList<int> in Java?

Show answer
Generic type parameters (the part in < >) must be object types, not primitives — you need the wrapper class Integer instead.

2. What is the wrapper class for double, and what is the process called when a double is automatically converted into it?

Show answer
Double; the automatic conversion is called autoboxing.
4.8

ArrayList Methods

An ArrayList is a resizable collection from java.util — unlike an array, it can grow and shrink as the program runs.

import java.util.ArrayList;

ArrayList<String> names = new ArrayList<>();
names.add("Ana");          // adds to the end
names.add("Ben");
names.add(0, "Cy");      // inserts at index 0, shifting others right

System.out.println(names.get(1));   // "Ana" — access by index
names.set(1, "Amy");        // replaces the element at index 1
names.remove("Ben");        // removes the first match by value
System.out.println(names.size());   // current element count
MethodDoes
add(x) / add(i, x)Append, or insert at index i
get(i)Return the element at index i
set(i, x)Replace the element at index i
remove(i) / remove(x)Remove by index, or first match by value
size()Current number of elements (like .length for arrays)
Watch the overload: remove(2) removes the element at index 2, but for an ArrayList<Integer>, remove(Integer.valueOf(2)) removes the element equal to 2 — an easy mix-up.
Practice

1. After ArrayList<Integer> list = new ArrayList<>(); list.add(5); list.add(10); list.add(15);, what does list.get(2) return?

Show answer
15 — indices are still 0-based, so index 2 is the third element.

2. Which method would you call to find out how many elements are currently in an ArrayList?

Show answer
.size()
4.9

ArrayList Traversals

ArrayLists can be traversed the same two ways as arrays — but removing elements while traversing forward is a classic bug source, because remaining elements shift down to fill the gap.

// standard for loop
for (int i = 0; i < names.size(); i++) {
    System.out.println(names.get(i));
}

// enhanced for loop
for (String n : names) {
    System.out.println(n);
}
// BUGGY: removing while traversing forward skips elements
for (int i = 0; i < names.size(); i++) {
    if (names.get(i).equals("Ben")) {
        names.remove(i);   // everything after shifts left, but i still moves forward
    }
}

// FIX: traverse backwards when removing
for (int i = names.size() - 1; i >= 0; i--) {
    if (names.get(i).equals("Ben")) {
        names.remove(i);
    }
}
Practice

1. Why does removing an element while looping forward risk skipping the next one?

Show answer
After remove(i), the element that used to be at i+1 shifts down into index i. But the loop's i++ still advances to i+1 next, so the shifted element at the old i position is never checked.

2. Why does looping backwards avoid that problem?

Show answer
When elements shift after a removal, they only shift into positions the backward loop has already passed — the not-yet-visited elements (at lower indices) are undisturbed.
4.10

Implementing ArrayList Algorithms

The same accumulator patterns (2.9, 4.5) work with ArrayLists — swap arr[i] for list.get(i) and arr.length for list.size().

ArrayList<Integer> nums = new ArrayList<>(List.of(4, 9, 2, 7));
int sum = 0;
for (int n : nums) {
    sum += n;
}

// building a NEW filtered list, rather than removing in place
ArrayList<Integer> evens = new ArrayList<>();
for (int n : nums) {
    if (n % 2 == 0) evens.add(n);
}
Common technique: when filtering, it's often simpler and safer to build a brand-new ArrayList of the elements you want to keep, rather than removing elements from the original list in place (avoiding the 4.9 shifting pitfall entirely).
Practice

1. Write a loop that counts how many Strings in an ArrayList<String> words start with the letter "A".

Show answer
int count = 0; for (String w : words) { if (w.charAt(0) == 'A') count++; }

2. Why is building a new filtered ArrayList often preferred over removing non-matching elements from the original while traversing?

Show answer
It sidesteps the index-shifting bug from 4.9 entirely — you only ever add to the new list, never remove while iterating.
4.11

2D Array Creation and Access

A 2D array models a grid — rows and columns — as an array of arrays.

int[][] grid = new int[3][4];   // 3 rows, 4 columns, all 0
grid[1][2] = 7;             // row 1, column 2

int[][] literal = {
    {1, 2, 3},
    {4, 5, 6}
};   // 2 rows, 3 columns

System.out.println(literal.length);       // 2 — number of rows
System.out.println(literal[0].length);    // 3 — length of row 0
  • Access always goes array[row][column] — row index first.
  • .length on the whole array gives the number of rows; .length on one row (array[i].length) gives that row's width.
  • Rows don't have to be the same length — this is called a jagged array — though most problems use uniform rectangular grids.
Practice

1. For int[][] g = new int[5][2];, how many rows and how many columns does it have?

Show answer
5 rows, 2 columns.

2. Given the literal array above, what does literal[1][0] evaluate to?

Show answer
4 — row 1 is {4, 5, 6}, and column 0 of that row is 4.
4.12

2D Array Traversals

Traversing a 2D array uses nested loops (2.11) — the outer loop walks rows, the inner loop walks columns within that row.

for (int row = 0; row < grid.length; row++) {
    for (int col = 0; col < grid[row].length; col++) {
        System.out.print(grid[row][col] + " ");
    }
    System.out.println();   // new line after each row
}

// enhanced for loop version — nested, since each "row" is itself an array
for (int[] r : grid) {
    for (int val : r) {
        System.out.print(val + " ");
    }
}
Why grid[row].length, not grid[0].length: using each row's own length correctly handles jagged arrays where rows differ in size; for uniform rectangular grids both give the same result.
Practice

1. In a nested traversal, which loop variable changes fastest — the row index or the column index?

Show answer
The column index (the inner loop) — it cycles through every value before the outer loop advances to the next row.

2. Write a nested loop that sums every value in a 2D int array grid.

Show answer
int sum = 0; for (int[] row : grid) { for (int v : row) { sum += v; } }
4.13

Implementing 2D Array Algorithms

2D array algorithms combine the nested-traversal pattern with accumulator logic — often tracked per row, per column, or across the whole grid.

int[][] grid = {{1,2,3},{4,5,6},{7,8,9}};

// sum of each row
for (int row = 0; row < grid.length; row++) {
    int rowSum = 0;
    for (int col = 0; col < grid[row].length; col++) {
        rowSum += grid[row][col];
    }
    System.out.println("Row " + row + " sum: " + rowSum);
}

// main diagonal (only defined for square grids)
int diagonalSum = 0;
for (int i = 0; i < grid.length; i++) {
    diagonalSum += grid[i][i];
}
Practice

1. Write a nested loop that finds the largest value anywhere in a 2D int array grid.

Show answer
int max = grid[0][0]; for (int[] row : grid) { for (int v : row) { if (v > max) max = v; } }

2. Why does the diagonal sum use a single loop, grid[i][i], instead of nested loops?

Show answer
The diagonal only has one cell per row (where row index equals column index), so a single counter walking both indices together is enough — no need to visit every column of every row.
4.14

Searching Algorithms

Two standard approaches for finding a value inside a collection:

AlgorithmRequiresRun time (2.12)
Linear searchNothing — works on any arrayO(n)
Binary searchArray must already be sortedO(log n)
// linear search — checks every element until found
public static int linearSearch(int[] arr, int target) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == target) return i;
    }
    return -1;   // not found
}

// binary search — repeatedly halves the search range (array MUST be sorted)
public static int binarySearch(int[] arr, int target) {
    int lo = 0, 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;
        else hi = mid - 1;
    }
    return -1;
}
Practice

1. Why can't binary search be used on an unsorted array?

Show answer
Binary search decides which half to discard by comparing the target to the middle element — that logic only works if the array is ordered, so the "wrong" half is guaranteed not to contain the target.

2. Searching an unsorted array of 1,000,000 elements for a value that isn't there — roughly how many comparisons does linear search need?

Show answer
About 1,000,000 — linear search must check every element before concluding it's absent, matching its O(n) run time.
4.15

Sorting Algorithms

Selection sort repeatedly finds the smallest remaining element and swaps it into its correct position.

public static void selectionSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) minIndex = j;
        }
        // swap the found minimum into position i
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

Each pass, the "sorted region" at the front grows by one element. With n elements, this takes roughly n passes, each scanning the shrinking remainder — giving O(n²) overall (2.12), same shape as a nested loop.

Swap pattern: swapping two array elements always needs a temporary variable to hold one value while the other is overwritten — arr[i] = arr[j]; arr[j] = arr[i]; would lose the original arr[i] value.
Practice

1. After the first outer-loop pass of selection sort on {5, 2, 8, 1}, what does the array look like?

Show answer
{1, 2, 8, 5} — the smallest value (1) is found and swapped into index 0; the rest are untouched by this pass.

2. Why does the outer loop only need to run to arr.length - 1, not arr.length?

Show answer
Once every element except the last is correctly placed, the last element is automatically in its correct spot too — there's nothing left to compare it against.
4.16

Recursion

A recursive method calls itself to solve a smaller version of the same problem. Every correct recursive method needs two parts:

  • A base case — the simplest input, handled directly with no further recursive call. Without one, the recursion never stops.
  • A recursive case — calls itself with an input that's closer to the base case than the current one.
public static int factorial(int n) {
    if (n == 0) return 1;             // base case
    return n * factorial(n - 1);   // recursive case
}
// factorial(4) → 4 * factorial(3) → 4 * (3 * factorial(2)) → ... → 4*3*2*1*1 = 24

Each call waits on the call stack for the call below it to return before it can finish its own multiplication — this is why recursion "unwinds" back up once it hits the base case.

Missing base case = infinite recursion — which eventually crashes with a StackOverflowError, the recursive equivalent of an infinite loop.
Practice

1. What is the base case of factorial above, and why is it necessary?

Show answer
n == 0, returning 1. Without it, the recursive calls would keep decreasing n forever (into negative numbers), never stopping.

2. Write a recursive method sum(n) that returns 1 + 2 + ... + n.

Show answer
public static int sum(int n) { if (n == 0) return 0; return n + sum(n - 1); }
4.17

Recursive Searching and Sorting

The searching and sorting ideas from 4.14–4.15 can be written recursively — each call handles one small piece, then recurses on the rest.

// recursive binary search
public static int binarySearch(int[] arr, int target, int lo, int hi) {
    if (lo > hi) return -1;                 // base case: range is empty, not found
    int mid = (lo + hi) / 2;
    if (arr[mid] == target) return mid;      // base case: found it
    else if (arr[mid] < target)
        return binarySearch(arr, target, mid + 1, hi);  // recurse right half
    else
        return binarySearch(arr, target, lo, mid - 1);  // recurse left half
}

Merge sort is a classic recursive sort: split the array in half, recursively sort each half, then merge the two sorted halves back together. Because it halves the problem each time (like binary search), it runs in O(n log n) — faster than selection sort's O(n²) on large inputs.

Pattern to notice: recursive binary search and iterative binary search (4.14) do the exact same comparisons — recursion just lets the "keep narrowing the range" idea be expressed as self-calls instead of a loop.
Practice

1. In the recursive binarySearch above, what are the two base cases?

Show answer
lo > hi (the search range is empty — target isn't present) and arr[mid] == target (the target was found).

2. Why is merge sort's O(n log n) considered faster than selection sort's O(n²) for large arrays?

Show answer
As n grows large, n² grows much faster than n log n — e.g. for n = 1,000,000, n² is a trillion while n log n is only around 20 million, a massive practical difference.