Free for students · Ad-free · WCAG 2.1 AA Compliant · Accessibility

Mock Exam #1: Brutal Trap Gauntlet

Designed to test your knowledge of the trickiest concepts and most common pitfalls on the AP exam. Can you spot the traps?

--:--:--

Section 1 — Multiple Choice (40 questions)

Question 1 of 40 · EASY

Consider the following code snippet:

int x = 5;
int y = 2;
double z = x / y;
System.out.println(z);

What is printed to the console?

A 2.5
B 2.0
C 2
D A compile-time error occurs.
Answer: B

This is a classic trap involving integer division. When you divide two integers in Java, the result is also an integer, with any fractional part discarded. So, x / y (which is 5 / 2) evaluates to 2, not 2.5. Only after this calculation is the integer 2 promoted to a double (2.0) to be stored in the variable z. So, 2.0 is what gets printed. To get 2.5, you would need to cast one of the operands to a double first, like (double) x / y.

Question 2 of 40 · MEDIUM

What is the output of the following code segment?

String greeting = "Hello, world!";
greeting.substring(0, 5);
System.out.println(greeting);
A Hello
B Hello,
C Hello, world!
D world!
Answer: C

Here's a core concept you must remember: String objects in Java are immutable. This means they cannot be changed once they are created. The substring method doesn't modify the original string; instead, it creates and returns a new string. In this code, the new string "Hello" that is returned by greeting.substring(0, 5) is never stored in a variable. The original greeting variable still points to the unchanged string "Hello, world!". Therefore, that's what gets printed.

Question 3 of 40 · MEDIUM

Consider the following class:

public class Counter {
    private static int checks = 0;

    public static boolean isPositive(int n) {
        checks++;
        return n > 0;
    }

    public static void main(String[] args) {
        // Assume checks is reset to 0 before this line
        boolean result = isPositive(-5) && isPositive(10);
        System.out.println(checks);
    }
}

What is printed when the main method is executed?

A 0
B 1
C 2
D The code results in an infinite loop.
Answer: B

This question tests your understanding of short-circuit evaluation with the logical AND (&&) operator. Java evaluates boolean expressions from left to right. With &&, if the first condition is false, the entire expression must be false, so Java doesn't bother evaluating the second condition. Here, isPositive(-5) is called first. It increments checks to 1 and returns false. Because the first part is false, the && short-circuits, and isPositive(10) is never called. Therefore, checks is only incremented once.

Question 4 of 40 · HARD

Consider the following method, which is intended to calculate the sum of all elements in a 2D integer array grid.

public static int findSum(int[][] grid) {
    int sum = 0;
    for (int i = 0; i < grid.length; i++) {
        for (int j = 0; j < grid[0].length; j++) {
            sum += grid[i][j];
        }
    }
    return sum;
}

Under which of the following conditions will this method fail and cause a run-time error?

A When `grid` is a square array (e.g., 3 rows, 3 columns).
B When `grid` has more columns than rows.
C When `grid` has at least one row that is shorter than the first row.
D The method will always work correctly, regardless of the dimensions of `grid`.
Answer: C

This is a very common mistake when working with 2D arrays. The inner loop's condition is j < grid[0].length, which means it always assumes that every row has the same number of columns as the first row (row 0). This works for rectangular arrays. However, if you have a 'ragged' array where a subsequent row is shorter than the first row, the inner loop will try to access an index that doesn't exist for that shorter row, throwing an ArrayIndexOutOfBoundsException. For example, if row 0 has 5 elements and row 1 has only 3, when i is 1, the inner loop will try to access grid[1][3], which is out of bounds.

Question 5 of 40 · MEDIUM

Examine the Gadget class below:

public class Gadget {
    private int id;

    public Gadget(int id) {
        id = id;
    }

    public int getId() {
        return id;
    }
}

What is the result of executing the following code?

Gadget myGadget = new Gadget(101);
System.out.println(myGadget.getId());
A 101
B 0
C null
D A compile-time error occurs.
Answer: B

This is a subtle but critical error called 'shadowing'. Inside the constructor public Gadget(int id), the parameter name id is the same as the instance variable name id. When you write id = id;, you are referring only to the parameter—you're assigning the parameter's value to itself. The instance variable id is never touched. Since instance variables of type int are given a default value of 0 if not explicitly initialized, the id field of the myGadget object remains 0. To fix this, you must use the this keyword to distinguish the instance variable: this.id = id;.

Question 6 of 40 · EASY

How many times will the string "Go Team!" be printed by the following for loop?

for (int i = 0; i <= 5; i++) {
    System.out.println("Go Team!");
}
A 4
B 5
C 6
D 7
Answer: C

Let's trace the values of i for which the loop condition i <= 5 is true. The loop will execute for i = 0, i = 1, i = 2, i = 3, i = 4, and i = 5. When i becomes 6, the condition 6 <= 5 is false, and the loop terminates. If you count them up, that is a total of 6 iterations. This is a common place to make an off-by-one error. Always be careful with < versus <= in your loop conditions!

Question 7 of 40 · MEDIUM

Consider the following code snippet:

ArrayList<Integer> scores = new ArrayList<Integer>();
scores.add(90);
scores.add(85);
scores.add(100);
scores.add(75);

scores.remove(2);

System.out.println(scores);

What is printed to the console?

A [90, 85, 75]
B [90, 100, 75]
C [90, 85, 100, 75]
D An error occurs because you cannot remove from a list of Integers with an int.
Answer: A

This question tests your knowledge of the overloaded remove method in ArrayList. When you pass an int to remove, like scores.remove(2), you are calling the remove(int index) version of the method. This removes the element at the specified index. In the list [90, 85, 100, 75], the element at index 2 is 100. After it's removed, the list becomes [90, 85, 75]. The common mistake is to think it removes the Integer object with the value 2, but there is no 2 in the list. To remove by value, you would need to use scores.remove(Integer.valueOf(100)).

Question 8 of 40 · MEDIUM

A game developer wants to generate a random integer to represent a standard six-sided die roll (values 1, 2, 3, 4, 5, or 6). Which of the following expressions correctly generates a random integer in the desired range?

A (int) (Math.random() * 6)
B (int) (Math.random() * 5) + 1
C (int) (Math.random() * 6) + 1
D (int) Math.random() * 6 + 1
Answer: C

Let's break this down. Math.random() gives you a double value in the range [0.0, 1.0), meaning it includes 0.0 but not 1.0.

  1. Math.random() * 6 produces a double in the range [0.0, 6.0).
  2. Casting to an int with (int) truncates (chops off) the decimal part. So, (int)(Math.random() * 6) gives you an integer from the set {0, 1, 2, 3, 4, 5}.
  3. Since we want a die roll from 1 to 6, we need to add 1 to this result. This shifts the range to {1, 2, 3, 4, 5, 6}. Therefore, (int) (Math.random() * 6) + 1 is the correct expression. Choice D is incorrect due to order of operations; it would cast Math.random() to 0 before multiplying.
Question 9 of 40 · MEDIUM

What are the contents of the array nums after the following code has been executed?

int[] nums = {10, 20, 30};
for (int val : nums) {
    val = val * 2;
}
A {20, 40, 60}
B {10, 20, 30}
C {10, 20, 60}
D The code will cause a compile-time error.
Answer: B

This question highlights a key behavior of the enhanced for loop (or for-each loop) with primitive data types like int. The loop variable, val, holds a copy of each element from the array, not a reference to the element itself. When the line val = val * 2; is executed, you are only changing the value of the temporary copy val. The original values in the nums array are unaffected. Therefore, after the loop finishes, nums still contains {10, 20, 30}.

Question 10 of 40 · HARD

Consider the following code segment:

String result = "";
String word = "AP";
for (int i = 0; i < word.length(); i++) {
    for (int j = 0; j < word.length(); j++) {
        if (i == j) {
            result += word.substring(i, i + 1);
        } else {
            result += "-";
        }
    }
}

What is the final value of the result string?

A A-P-
B -A-P
C A--P
D AP
Answer: C

This problem requires you to carefully trace the execution of nested loops. Let's go step-by-step:

  • Outer loop starts: i = 0. word.length() is 2.
    • Inner loop j = 0: i == j is true. result becomes "A".
    • Inner loop j = 1: i == j is false. result becomes "A-".
  • Outer loop continues: i = 1.
    • Inner loop j = 0: i == j is false. result becomes "A--".
    • Inner loop j = 1: i == j is true. result becomes "A--P". The loops finish, and the final value of result is "A--P". Tracing problems like this are all about patience and keeping track of your variables one step at a time.
Question 11 of 40 · HARD

Assume a Student class exists with a constructor public Student(String name) and a method public void setName(String newName). Consider the following code:

public class TestDriver {
    public void changeStudent(Student s) {
        s.setName("Priya");
        s = new Student("Carlos");
        s.setName("Jordan");
    }

    public void runTest() {
        Student maya = new Student("Maya");
        changeStudent(maya);
        System.out.println(maya.getName());
    }
}

What is printed when runTest is called?

A Maya
B Priya
C Carlos
D Jordan
Answer: B

This is a tricky but fundamental concept in Java: pass-by-value. When you pass an object like maya to a method, you are passing a copy of the reference (the object's memory address).

  1. Inside changeStudent, the local variable s initially points to the same Student object as maya.
  2. s.setName("Priya") follows this reference and successfully changes the name of the original object to "Priya".
  3. s = new Student("Carlos") is the key part. This line does NOT change the original maya object. It only changes the local variable s to point to a brand new Student object. The maya variable in runTest is completely unaffected by this reassignment.
  4. s.setName("Jordan") then changes the name of this new, second object, which is then discarded when the method ends. So, the original object that maya refers to had its name changed to "Priya" and that's it.
Question 12 of 40 · EASY

Given the following array declaration:

String[] days = {"Mon", "Tue", "Wed", "Thu", "Fri"};

What is printed by the statement System.out.println(days[3]);?

A Wed
B Thu
C Fri
D An `ArrayIndexOutOfBoundsException` is thrown.
Answer: B

Remember that arrays in Java are zero-indexed, which means the first element is at index 0. So, in the days array:

  • days[0] is "Mon"
  • days[1] is "Tue"
  • days[2] is "Wed"
  • days[3] is "Thu"
  • days[4] is "Fri" The statement asks for the element at index 3, which is "Thu".
Question 13 of 40 · MEDIUM

According to De Morgan's laws, which of the following boolean expressions is equivalent to !(x > 5 && y < 10)?

A x <= 5 && y >= 10
B x < 5 || y > 10
C x <= 5 || y >= 10
D x > 5 || y < 10
Answer: C

De Morgan's laws are essential for simplifying boolean expressions. The law for an AND expression states that !(A && B) is equivalent to !A || !B. Let's apply that here:

  • A is x > 5. The negation, !A, is x <= 5.
  • B is y < 10. The negation, !B, is y >= 10.
  • The && becomes an ||. Putting it all together, !(x > 5 && y < 10) becomes x <= 5 || y >= 10. Be very careful with the negations: the opposite of > is <=, and the opposite of < is >=.
Question 14 of 40 · MEDIUM

An array data contains n distinct integers. A standard linear search algorithm is used to find a target value k. In which of the following situations will the algorithm perform the maximum number of comparisons?

A When `k` is the first element in the array.
B When `k` is located at the middle index, `n / 2`.
C When `k` is the last element in the array or is not in the array at all.
D The number of comparisons is always `n`, regardless of the position of `k`.
Answer: C

A linear search works by checking each element sequentially, starting from the beginning. The best case is finding the element at the first position (1 comparison). The number of comparisons increases as the element's position gets further down the array. The absolute worst-case scenario occurs when the algorithm has to check every single element. This happens in two cases: 1) the target element is the very last one in the array, or 2) the target element isn't in the array, forcing the search to go through all n elements before concluding it's not there. Both of these situations require the maximum number of comparisons.

Question 15 of 40 · HARD

Consider the following declarations:

String s1 = "Shrutam";
String s2 = "Shrutam";
String s3 = new String("Shrutam");
String s4 = s1;

Which of the following expressions evaluates to false?

A s1.equals(s3)
B s1 == s2
C s1 == s3
D s1 == s4
Answer: C

This question tests the critical difference between == (reference equality) and .equals() (content equality) for objects.

  • s1.equals(s3) is true because .equals() checks if the character sequences are the same, which they are.
  • s1 == s2 is true. Because s1 and s2 are both string literals with the same value, the Java compiler optimizes by placing them in a 'string pool', making both variables point to the exact same object in memory.
  • s1 == s4 is true because s4 is explicitly assigned the reference of s1. They point to the same object.
  • s1 == s3 is false. The new keyword forces the creation of a new, separate String object in memory, even if its content is identical to an existing one. Since s1 and s3 point to different objects in memory, the == operator, which checks if the references are the same, returns false.
Question 16 of 40 · EASY

A Car class is defined with a private instance variable for its speed:

public class Car {
    private int speed;
    // ... other methods
}

In a separate class, Traffic, a programmer attempts to write the following code:

// Inside some method in the Traffic class
Car myCar = new Car();
myCar.speed = 55; // Set the car's speed

What is the result of attempting to compile and run this code?

A The code compiles and runs, setting the car's speed to 55.
B The code compiles, but a run-time error occurs.
C The code fails to compile.
D The code compiles and runs, but the speed remains at its default value of 0.
Answer: C

This is a direct test of access modifiers. The speed instance variable in the Car class is declared as private. This means it can only be accessed from within the Car class itself. The Traffic class is an external class, so it does not have permission to directly access or modify myCar.speed. This is a violation of encapsulation rules, and the Java compiler will catch it, resulting in a compile-time error. To allow external classes to modify the speed, the Car class would need to provide a public method, like public void setSpeed(int newSpeed).

Question 17 of 40 · MEDIUM

What is the result of executing the following code segment?

int k = 0;
int count = 0;
while (k < 10) {
    if (k % 2 == 0) {
        k++;
    }
    count++;
}
System.out.println(count);
A 10
B 11
C The code prints a value, but it is unpredictable.
D The code results in an infinite loop.
Answer: D

Let's trace the loop carefully. The variable k is only incremented inside the if statement, which only runs when k is even.

  • Iteration 1: k is 0. k < 10 is true. k % 2 == 0 is true. k becomes 1. count becomes 1.
  • Iteration 2: k is 1. k < 10 is true. k % 2 == 0 is false. The if block is skipped. k remains 1. count becomes 2.
  • Iteration 3: k is 1. k < 10 is true. k % 2 == 0 is false. The if block is skipped. k remains 1. count becomes 3. At this point, k is stuck at 1. It will never become even again, so it will never be incremented. The while (k < 10) condition will therefore always be true, leading to an infinite loop.
Question 18 of 40 · MEDIUM

What are the contents of the names list after the following code is executed?

ArrayList<String> names = new ArrayList<String>();
names.add("Aaliyah");
names.add("Ben");
names.add("Carlos");
names.add("Priya");

for (int i = 0; i < names.size(); i++) {
    if (names.get(i).length() < 5) {
        names.remove(i);
    }
}
A ["Aaliyah", "Carlos", "Priya"]
B ["Aaliyah", "Priya"]
C ["Aaliyah", "Carlos"]
D An `IndexOutOfBoundsException` occurs.
Answer: A

This is a classic and tricky problem that happens when you remove elements from an ArrayList while iterating forward. When you remove an element, the size of the list shrinks, and all subsequent elements shift to the left.

  1. i = 0: names.get(0) is "Aaliyah" (length 7). The if is false.
  2. i = 1: names.get(1) is "Ben" (length 3). The if is true. names.remove(1) is called. The list is now ["Aaliyah", "Carlos", "Priya"]. "Carlos" has shifted from index 2 to index 1.
  3. Loop continues: i increments to 2. The loop condition i < names.size() (2 < 3) is true. The loop now checks names.get(2), which is "Priya". The element "Carlos", which is now at index 1, was skipped entirely!
  4. i = 2: names.get(2) is "Priya" (length 5). The if is false.
  5. Loop ends. The final list is ["Aaliyah", "Carlos", "Priya"].
Question 19 of 40 · MEDIUM

Consider the following recursive method:

public int mystery(int n) {
    if (n < 10) {
        return n;
    } else {
        return mystery(n / 10) + (n % 10);
    }
}

What value is returned by the call mystery(345)?

A 3
B 12
C 543
D 345
Answer: B

This recursive method is designed to sum the digits of an integer. Let's trace the calls:

  1. mystery(345) is called. Since 345 is not less than 10, it returns mystery(345 / 10) + (345 % 10). This simplifies to mystery(34) + 5.
  2. Now we must solve mystery(34). Since 34 is not less than 10, it returns mystery(34 / 10) + (34 % 10). This simplifies to mystery(3) + 4.
  3. Now we must solve mystery(3). Since 3 is less than 10, the base case is met, and the method returns 3.
  4. Now we can substitute back. The call from step 2, mystery(3) + 4, becomes 3 + 4, which is 7.
  5. Finally, we substitute this into step 1. The call mystery(34) + 5 becomes 7 + 5, which is 12. The final result is 12.
Question 20 of 40 · EASY

What is the final value of the grade variable after this code segment is executed?

int score = 85;
String grade = "F";
if (score >= 90)
    grade = "A";
else if (score >= 80)
    if (score >= 88)
        grade = "A-";
    else
        grade = "B+";
A "A"
B "A-"
C "B+"
D "F"
Answer: C

This question tests the 'dangling else' rule. An else statement always pairs with the nearest preceding if that doesn't already have an else. Let's trace the logic:

  1. score is 85.
  2. The first condition, score >= 90, is false.
  3. The code moves to the else if (score >= 80). This condition is true.
  4. Now we enter the code associated with that else if. It's a nested if: if (score >= 88). This condition is false (85 is not >= 88).
  5. Because the nested if is false, its corresponding else block is executed. The else sets grade = "B+". The common mistake is to think this else belongs to the very first if, but it belongs to the closest one, if (score >= 88).
Question 21 of 40 · MEDIUM

Consider the following GameGrid class. A GameGrid object represents a 2D grid of scores for a game played in a tournament.

public class GameGrid {
    private int[][] scores;

    // constructor and other methods not shown

    /**
     * Calculates the total score for a specific player (column).
     * @param playerIndex the column index of the player.
     * @return the sum of scores in that column.
     */
    public int getPlayerTotal(int playerIndex) {
        int total = 0;
        for (int r = 0; r < scores.length; r++) {
            total += scores[r][playerIndex];
        }
        return total;
    }
}

A GameGrid object gg is created and its scores instance variable is initialized as follows:

int[][] scores = {{15, 22, 18}, {20, 25, 21}, {12, 16, 30}, {28, 24, 26}};

What is printed as a result of executing System.out.println(gg.getPlayerTotal(1));?

A 55
B 87
C 75
D An `ArrayIndexOutOfBoundsException` is thrown.
Answer: B

Let's trace this together! The method getPlayerTotal is asked to find the total for playerIndex 1. This means we're summing up all the scores in column 1.

The scores grid is: { {15, 22, 18}, // row 0 {20, 25, 21}, // row 1 {12, 16, 30}, // row 2 {28, 24, 26} } // row 3

The loop iterates through the rows (r from 0 to 3) and adds the element at scores[r][1] each time.

  • When r is 0, it adds scores[0][1], which is 22.
  • When r is 1, it adds scores[1][1], which is 25. (Total is 22 + 25 = 47)
  • When r is 2, it adds scores[2][1], which is 16. (Total is 47 + 16 = 63)
  • When r is 3, it adds scores[3][1], which is 24. (Total is 63 + 24 = 87)

The final total is 87. This is why B is correct.

Choice A is tempting; it's the sum of row 1 (20 + 25 + 21 = 66, actually, so not even that). Choice C is the sum of column 2 (18+21+30+26=95). A common mistake is to mix up rows and columns. Remember, the first index is the row, and the second is the column: [row][col]. Choice D is incorrect because the loop bounds (r < scores.length, which is 4) and the column index (1) are both valid for this grid.

Question 22 of 40 · MEDIUM

What is the value of count after the following code segment has finished executing?

int count = 0;
for (int outer = 5; outer > 0; outer -= 2) {
    for (int inner = 0; inner < outer; inner++) {
        count++;
    }
}
A 5
B 8
C 9
D 15
Answer: C

This is a great problem for practicing tracing nested loops. Let's walk through the outer loop step by step.

  • outer is 5: The inner loop runs for inner = 0, 1, 2, 3, 4. That's 5 times. count becomes 5.
  • outer is 3: The outer loop updates by outer -= 2, so it's now 3. The inner loop runs for inner = 0, 1, 2. That's 3 times. count becomes 5 + 3 = 8.
  • outer is 1: The outer loop updates by outer -= 2 again, so it's now 1. The inner loop runs for inner = 0. That's 1 time. count becomes 8 + 1 = 9.

After this, outer becomes -1. The condition outer > 0 is now false, and the loop terminates. The final value of count is 9.

Choice D (15) is what you'd get if the outer loop was outer-- (5+4+3+2+1). Choice B (8) is a common mistake where you might forget the final iteration of the outer loop when outer is 1. Choice A (5) is what you'd get if you only traced the first iteration of the outer loop.

Question 23 of 40 · HARD

Consider the following recursive method mystery.

public String mystery(String s) {
    if (s.length() <= 1) {
        return s;
    }
    String sub = s.substring(1, s.length() - 1);
    return s.charAt(s.length() - 1) + mystery(sub) + s.charAt(0);
}

What is returned by the call mystery("APPLE")?

A ELPPA
B EPLPA
C AELPP
D ELPA
Answer: A

Recursive string problems can feel like a puzzle. The key is to trace the calls one by one until you hit the base case, and then build the answer back up.

The base case is s.length() <= 1. The recursive step takes the last character, adds the result of mystery on the inner part of the string, and then adds the first character.

  1. mystery("APPLE") calls mystery("PPL"). It will return: 'E' + mystery("PPL") + 'A'.
  2. mystery("PPL") calls mystery("P"). It will return: 'L' + mystery("P") + 'P'.
  3. mystery("P") hits the base case (length is 1) and returns "P".

Now, let's substitute back up:

  • The call from step 2 gets "P" back, so it returns 'L' + "P" + 'P', which is "LPP".
  • The call from step 1 gets "LPP" back, so it returns 'E' + "LPP" + 'A', which is "ELPPA".

This is why A is the correct answer. The other choices come from small mistakes in the recursive logic. Choice B might come from mixing up the order of concatenation. Choice C might come from putting the charAt(0) first. Choice D could happen if you made an off-by-one error in the substring call, for example using s.length() - 2 and losing a character.

Question 24 of 40 · MEDIUM

What is printed by the following code segment?

String event = "AP-CSA-EXAM";
int dash1 = event.indexOf("-");
int dash2 = event.indexOf("-", dash1 + 1);

String result = event.substring(dash1 + 1, dash2);

System.out.println(result);
A AP
B CSA
C EXAM
D P-CSA-E
Answer: B

This question tests your understanding of some key String methods. Let's break it down.

  1. String event = "AP-CSA-EXAM"; Our string is "AP-CSA-EXAM".
  2. int dash1 = event.indexOf("-"); This finds the index of the first occurrence of "-". The first dash is at index 2. So, dash1 is 2.
  3. int dash2 = event.indexOf("-", dash1 + 1); This finds the index of the next occurrence of "-", but it starts searching from index dash1 + 1 (which is index 3). The next dash is at index 6. So, dash2 is 6.
  4. String result = event.substring(dash1 + 1, dash2); This is the crucial step. We're taking a substring from index 2 + 1 (which is 3) up to (but not including) index 6.

The characters at indices 3, 4, and 5 are 'C', 'S', and 'A'. So, the substring is "CSA".

This is where many students slip up: remembering that the second parameter of substring is the endIndex and it's exclusive. The character at the endIndex is not included in the result. Choice D is a common error if you misunderstand how substring's parameters work.

Question 25 of 40 · MEDIUM

Consider the Student class below.

public class Student {
    private String name;
    private int id;

    public Student(String name, int id) {
        name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }
}

What is printed by the following code?

Student s1 = new Student("Priya", 12345);
System.out.println(s1.getName());
A Priya
B null
C 12345
D A compile-time error occurs.
Answer: B

This is a classic trap involving the this keyword! Look closely at the constructor:

public Student(String name, int id) { name = name; this.id = id; }

In the line name = name;, both names refer to the parameter name. The parameter is being assigned to itself, which does nothing. The instance variable private String name; is never assigned a value. This is called "shadowing"—the parameter name hides the instance variable name.

To fix this, the line should have been this.name = name;. The this keyword is used to specify that we mean the instance variable that belongs to this object.

Since the instance variable name was never initialized, it retains its default value, which for an object type like String is null. Therefore, s1.getName() returns null.

Question 26 of 40 · HARD

Consider the following code segment. What are the contents of values after the code has executed?

ArrayList<Integer> values = new ArrayList<Integer>();
values.add(2);
values.add(4);
values.add(5);
values.add(6);
values.add(7);

for (int i = 0; i < values.size(); i++) {
    if (values.get(i) % 2 == 0) {
        values.remove(i);
    }
}
A [5, 7]
B [4, 5, 7]
C [2, 5, 7]
D An `IndexOutOfBoundsException` is thrown.
Answer: B

This is one of the most common and tricky ArrayList traps on the AP exam. When you remove an element from an ArrayList using an index, all subsequent elements shift to the left. If you are iterating forward with an index-based loop, this can cause you to skip the very next element.

Let's trace it carefully:

  • Initial values: [2, 4, 5, 6, 7]
  • i = 0: values.get(0) is 2 (even). values.remove(0) is called. The list becomes [4, 5, 6, 7]. The loop finishes, and i increments to 1.
  • i = 1: Here's the trap! The list has shifted. values.get(1) is now 5, not 4. The 4 was skipped because it moved into the index i just passed over. 5 is not even, so nothing happens. i increments to 2.
  • i = 2: values.get(2) is 6 (even). values.remove(2) is called. The list becomes [4, 5, 7]. i increments to 3.
  • i = 3: The loop condition i < values.size() is now 3 < 3, which is false. The loop terminates.

The final state of values is [4, 5, 7].

Choice A is what you might expect if the code worked perfectly and removed all even numbers. To do that correctly, you should iterate backward from values.size() - 1 down to 0, or use an Iterator.

Question 27 of 40 · MEDIUM

Which of the following boolean expressions is equivalent to !(x > 5 && y <= 10) for all integer values of x and y?

A x < 5 && y > 10
B x <= 5 && y > 10
C x < 5 || y > 10
D x <= 5 || y > 10
Answer: D

This question is a direct application of De Morgan's Laws, which are essential for manipulating boolean expressions. De Morgan's Laws state:

  1. !(A && B) is equivalent to !A || !B
  2. !(A || B) is equivalent to !A && !B

In our problem, we have !(x > 5 && y <= 10). Let's identify our A and B:

  • A is (x > 5)
  • B is (y <= 10)

Applying the first law, !(A && B) becomes !A || !B.

  • !A is !(x > 5), which is x <= 5.
  • !B is !(y <= 10), which is y > 10.

Putting it together with the || (OR), we get x <= 5 || y > 10.

This is a place where you need to be very careful with the details. A common mistake is to forget to flip the && to an || (Choice B), or to get the negations wrong, like mixing up < with <= (Choice C).

Question 28 of 40 · MEDIUM

An integer array arr is being sorted using insertion sort. The array's contents after the first three passes of the main loop are shown below.

  • Initial array: [8, 3, 9, 5, 2]
  • After 1st pass: [3, 8, 9, 5, 2]
  • After 2nd pass: [3, 8, 9, 5, 2]
  • After 3rd pass: [3, 5, 8, 9, 2]

What will be the contents of the array after the 4th pass?

A [2, 3, 5, 8, 9]
B [3, 5, 8, 2, 9]
C [3, 2, 5, 8, 9]
D [3, 5, 8, 9, 2]
Answer: A

Let's remember how insertion sort works. It builds the final sorted array one item at a time. For each pass i, it takes the element at index i and 'inserts' it into its correct place within the already sorted portion of the array (from index 0 to i-1).

The state of the array after the 3rd pass is [3, 5, 8, 9, 2]. The sorted portion is [3, 5, 8, 9]. The next element to consider is at index 4, which is 2.

For the 4th pass, we take the element 2 and find its correct place in the sorted part [3, 5, 8, 9]. The 2 needs to move all the way to the beginning. The other elements (3, 5, 8, 9) will be shifted one position to the right to make room.

The result is [2, 3, 5, 8, 9], which is the fully sorted array.

Choice B is a common mistake if you confuse insertion sort with selection sort. Selection sort would have found the 2 and swapped it with the element at index 4, which is 9, resulting in [3, 5, 8, 2, 9]. But insertion sort shifts elements, it doesn't just swap with the current position.

Question 29 of 40 · EASY

Consider the following ScoreKeeper class and code segment.

public class ScoreKeeper {
    public int score = 0;
}

// in another class's main method:
ScoreKeeper teamA = new ScoreKeeper();
ScoreKeeper teamB = teamA;

teamB.score = 100;

System.out.println(teamA.score);

What is printed when the code segment is executed?

A 0
B 100
C null
D A compile-time error occurs.
Answer: B

This question gets at the heart of how object references work in Java. It's a concept called aliasing.

When you write ScoreKeeper teamA = new ScoreKeeper();, you create a new ScoreKeeper object in memory, and the variable teamA holds the memory address of that object. Think of teamA as a label pointing to the object.

When you then write ScoreKeeper teamB = teamA;, you are not creating a new object. You are creating a second label, teamB, and telling it to point to the exact same object that teamA points to.

So, teamA and teamB are two different names for the same thing. When you execute teamB.score = 100;, you are changing the score variable of that one object. Since teamA still points to that same object, asking for teamA.score will give you the new value, 100.

The most common incorrect answer is A, which comes from thinking that teamB = teamA creates a separate copy of the object. It does not!

Question 30 of 40 · MEDIUM

Consider the Game class below.

public class Game {
    private static int totalGamesPlayed = 0;
    private int score;

    public Game(int score) {
        this.score = score;
        totalGamesPlayed++;
    }

    public static int getTotalGames() {
        return totalGamesPlayed;
    }

    public int getScore() {
        return score;
    }
}

Which of the following code segments, if placed in a method in a different class, would cause a compile-time error?

A Game g1 = new Game(150); System.out.println(g1.getTotalGames());
B System.out.println(Game.getTotalGames());
C Game g2 = new Game(200); System.out.println(g2.getScore());
D System.out.println(Game.getScore());
Answer: D

This question tests the difference between static (class) members and instance (object) members.

  • static members belong to the class itself. You can access them using the class name, like Game.totalGamesPlayed or Game.getTotalGames().
  • Instance members belong to a specific object (an instance of the class). You need to create an object first to access them, like g2.score or g2.getScore().

Let's analyze the choices:

  • A: This is valid. Although it's better style to call a static method using the class name (Game.getTotalGames()), Java allows you to call it on an instance (g1.getTotalGames()). It won't cause a compile error.
  • B: This is the standard, correct way to call a static method. It's valid.
  • C: This is the standard, correct way to call an instance method. You create an instance g2 and then call getScore() on it. It's valid.
  • D: This is the error. getScore() is an instance method. It needs to know which game's score to return. You cannot call it on the Game class itself. You must call it on an object, like g1.getScore() or g2.getScore(). Trying to access an instance method from a static context (using the class name) will result in a compile-time error.
Question 31 of 40 · MEDIUM

A 2D array mat is initialized as follows:

int[][] mat = {{10, 20, 30}, {40, 50, 60}, {70, 80, 90}};

What is the value of sum after the following code is executed?

int sum = 0;
for (int i = 0; i < mat.length; i++) {
    sum += mat[i][mat.length - 1 - i];
}
A 150
B 120
C 90
D 210
Answer: A

This code is designed to sum the elements along one of the diagonals of a square matrix. Let's trace the loop to see which one.

The matrix mat is: { {10, 20, 30}, {40, 50, 60}, {70, 80, 90} }

mat.length is 3, which is the number of rows.

The loop runs for i = 0, 1, and 2. The expression for the column index is mat.length - 1 - i, which is 3 - 1 - i, or 2 - i.

  • i = 0: It adds mat[0][2 - 0], which is mat[0][2] or 30. sum is 30.
  • i = 1: It adds mat[1][2 - 1], which is mat[1][1] or 50. sum is 30 + 50 = 80.
  • i = 2: It adds mat[2][2 - 2], which is mat[2][0] or 70. sum is 80 + 70 = 150.

This is the sum of the anti-diagonal (from top-right to bottom-left). The final sum is 150.

Choice B (120) is the sum of the middle column. Choice C (90) is the value of the last element. A common mistake is to sum the main diagonal (mat[i][i]), which would be 10 + 50 + 90 = 150. In this specific case, both diagonals happen to have the same sum, but it's crucial to trace the correct one!

Question 32 of 40 · HARD

What is the output of the following code segment?

String s = null;
int count = 0;

if (s == null || s.length() > 5) {
    count++;
}

if (s != null && s.length() > 0) {
    count++;
}

System.out.println(count);
A 0
B 1
C 2
D A `NullPointerException` is thrown.
Answer: B

This is a fantastic question about short-circuit evaluation, a key feature of Java's logical operators.

Let's look at the first if statement: if (s == null || s.length() > 5).

  • The first part, s == null, is evaluated. Since s is null, this is true.
  • Because this is an || (OR) expression, if the first part is true, the entire expression must be true. Java is efficient and knows this, so it does not evaluate the second part, s.length() > 5. This is called short-circuiting. If it had tried to evaluate s.length(), it would have thrown a NullPointerException!
  • Since the condition is true, count is incremented to 1.

Now for the second if statement: if (s != null && s.length() > 0).

  • The first part, s != null, is evaluated. This is false.
  • Because this is an && (AND) expression, if the first part is false, the entire expression must be false. Again, Java short-circuits and does not evaluate the second part.
  • Since the condition is false, the code inside the if block is skipped. count remains 1.

Finally, System.out.println(count); prints the final value of count, which is 1. The big trap here is choice D. If you don't know about short-circuiting, you would expect s.length() to cause a NullPointerException.

Question 33 of 40 · EASY

What is printed by the following code segment?

int a = 25;
int b = 10;
System.out.println(a / b + " " + a % b);
A 2.5 5
B 2 5
C 3 5
D 2.5 0
Answer: B

This question tests two fundamental arithmetic operators in Java: integer division (/) and modulo (%).

  • a / b: Since both a and b are int variables, this is integer division. 25 / 10 evaluates to 2. The fractional part (.5) is simply discarded, not rounded. So the result is 2.
  • a % b: The modulo operator gives the remainder of a division. 25 divided by 10 is 2 with a remainder of 5. So, 25 % 10 is 5.

The println statement concatenates these results with a space in between, so the output is 2 5.

A common mistake is to think a / b will produce a double like 2.5 because that's the mathematical answer. But in Java, the type of the result depends on the types of the operands. int / int always results in an int.

Question 34 of 40 · MEDIUM

An array of n distinct integers is sorted in ascending order. Which of the following statements best compares the number of comparisons needed to find a specific value using binary search versus sequential search in the worst-case scenario?

A Binary search will require approximately `n/2` comparisons, while sequential search will require approximately `n` comparisons.
B Binary search will require approximately `log₂(n)` comparisons, while sequential search will require approximately `n` comparisons.
C Both searches will require approximately `n` comparisons because in the worst case, the element is at the end of the array.
D Binary search will require approximately `n` comparisons, while sequential search will require approximately `log₂(n)` comparisons.
Answer: B

This question compares the efficiency of two fundamental search algorithms.

  • Sequential Search: In the worst-case scenario (the item is at the very end of the array or not in the array at all), sequential search must look at every single element. For an array of size n, this is n comparisons.

  • Binary Search: This is a much more efficient algorithm, but it requires the array to be sorted. It works by repeatedly dividing the search interval in half. If you have n items, the first comparison cuts the problem size to n/2, the second to n/4, and so on. The number of times you can divide n by 2 until you get to 1 is given by the logarithm base 2 of n, or log₂(n). This is a dramatically smaller number than n for large n.

Therefore, binary search takes approximately log₂(n) comparisons in the worst case, while sequential search takes n. Choice B correctly identifies this relationship. Choice A incorrectly states the number of comparisons for binary search. Choice D swaps the efficiencies, and Choice C incorrectly claims both are the same, ignoring the power of binary search.

Question 35 of 40 · MEDIUM

Consider the Gadget class below.

public class Gadget {
    private String name;
    private int version;

    public Gadget(String name) {
        this(name, 1);
    }

    public Gadget(String name, int version) {
        this.name = name;
        this.version = version;
    }

    // accessor methods not shown
}

Which of the following code segments will create a Gadget object named "Phone" with a version of 1?

A `Gadget g = new Gadget("Phone");`
B `Gadget g = new Gadget("Phone", 0);`
C `Gadget g = new Gadget(); g.name = "Phone"; g.version = 1;`
D `Gadget g = new Gadget(1, "Phone");`
Answer: A

This question is about constructor overloading and how one constructor can call another using this().

Let's look at the constructors:

  1. public Gadget(String name): This is a one-parameter constructor. Its only job is to call the other constructor, passing along the name it received and a default version of 1. The line this(name, 1); is a call to the two-parameter constructor from within the one-parameter constructor.
  2. public Gadget(String name, int version): This is a two-parameter constructor that directly initializes the instance variables.

We want to create a Gadget with name "Phone" and version 1.

  • Choice A: new Gadget("Phone") calls the one-parameter constructor. That constructor then immediately calls the two-parameter constructor as new Gadget("Phone", 1). This sets the name to "Phone" and the version to 1. This is exactly what we want.
  • Choice B: This would create a gadget with version 0, not 1.
  • Choice C: This would not compile because the instance variables name and version are private, so they cannot be accessed directly from outside the class. Also, there is no no-argument constructor Gadget() defined.
  • Choice D: This would not compile because there is no constructor that takes an int and then a String. The order of parameters matters.
Question 36 of 40 · HARD

What is the output of the following code segment?

Integer a = 150;
Integer b = 150;
Integer c = 100;
Integer d = 100;

System.out.print(a == b);
System.out.print(", ");
System.out.print(c == d);
A true, true
B false, true
C false, false
D true, false
Answer: B

This is a very tricky question that hinges on a specific implementation detail of Java's wrapper classes. It's about the difference between comparing object references (==) and comparing their actual values (.equals()).

When you write Integer a = 150;, Java uses a process called autoboxing to convert the int literal 150 into an Integer object. It's equivalent to Integer a = Integer.valueOf(150);.

Here's the secret: to save memory, Java maintains a cache of Integer objects for small integer values. By default, this cache is for values from -128 to 127.

  • c == d: Both c and d are assigned the value 100. Since 100 is within the cached range [-128, 127], Java gives both c and d a reference to the exact same pre-existing Integer object from the cache. Therefore, c == d compares two references pointing to the same object, and the result is true.

  • a == b: Both a and b are assigned the value 150. This value is outside the cached range. Because of this, Java creates a new Integer(150) for a and a separate new Integer(150) for b. They have the same value, but they are two different objects in memory. Therefore, a == b compares two references pointing to different objects, and the result is false.

The output is false, true. This is a classic AP exam trap! The safe way to compare wrapper objects for equality is to always use the .equals() method, as in a.equals(b), which would be true.

Question 37 of 40 · EASY

Consider the following while loop.

int k = 2;
while (k <= 10) {
    System.out.print(k + " ");
    k += 2;
}

Which of the following for loops produces the exact same output?

A ```java for (int k = 2; k < 10; k += 2) { System.out.print(k + " "); } ```
B ```java for (int k = 2; k <= 10; k++) { System.out.print(k + " "); } ```
C ```java for (int k = 2; k <= 10; k += 2) { System.out.print(k + " "); } ```
D ```java for (int k = 0; k <= 10; k += 2) { System.out.print(k + " "); } ```
Answer: C

A for loop is really just a compact way of writing a certain kind of while loop. A for loop has three parts: for (initialization; condition; update).

Let's break down the given while loop:

  • Initialization: int k = 2; (This happens before the loop starts.)
  • Condition: k <= 10 (This is checked at the beginning of each iteration.)
  • Update: k += 2; (This happens at the end of each iteration.)

To create an equivalent for loop, we just need to place these three parts in the correct spots.

for (int k = 2; k <= 10; k += 2)

This matches the logic of the while loop perfectly. The loop will start with k=2, continue as long as k is less than or equal to 10, and add 2 to k after each time through. This makes Choice C the correct answer.

  • Choice A has the wrong condition (k < 10), so it would not print the final 10.
  • Choice B has the wrong update step (k++), so it would print 2 3 4 5 6 7 8 9 10.
  • Choice D has the wrong initialization (k = 0), so it would start by printing 0.
Question 38 of 40 · MEDIUM

Consider the Book class and the findBook method below.

public class Book {
    private String title;
    public Book(String title) { this.title = title; }
    public String getTitle() { return this.title; }
}

public Book findBook(Book[] library, String targetTitle) {
    for (Book b : library) {
        if (b.getTitle().equals(targetTitle)) {
            return b;
        }
    }
    return null;
}

What happens when the following code is executed?

Book[] myBooks = {new Book("Dune"), new Book("Foundation")};
Book found = findBook(myBooks, "Hyperion");
System.out.println(found.getTitle());
A Dune
B Hyperion
C null
D A `NullPointerException` is thrown.
Answer: D

Let's trace the execution carefully. The findBook method searches the myBooks array for a book with the title "Hyperion".

  1. The myBooks array contains books titled "Dune" and "Foundation".
  2. The findBook method loops through the array. It checks "Dune", which doesn't match "Hyperion". It checks "Foundation", which also doesn't match.
  3. The loop finishes without finding a match. The method then executes its final line: return null;.
  4. The variable found in the calling code is assigned the value null.
  5. The next line is System.out.println(found.getTitle());. Here's the problem. You are trying to call the getTitle() method on the found variable, which currently holds null. You can't call a method on null! It's like trying to ask a question to someone who isn't there.

This action causes a NullPointerException at runtime. Choice C is a tempting distractor, as null is what's inside the found variable, but the code doesn't just print the variable—it tries to use it, which causes the error.

Question 39 of 40 · MEDIUM

The following code segment is intended to shift all elements of an integer array data one position to the left, with the first element moving to the last position. For example, if data is {10, 20, 30, 40}, it should become {20, 30, 40, 10}.

int[] data = {10, 20, 30, 40};
int first = data[0];

// Missing loop

data[data.length - 1] = first;

Which of the following loops should replace // Missing loop to make the code work as intended?

A ```java for (int i = 1; i < data.length; i++) { data[i] = data[i - 1]; } ```
B ```java for (int i = 0; i < data.length - 1; i++) { data[i] = data[i + 1]; } ```
C ```java for (int i = data.length - 1; i > 0; i--) { data[i - 1] = data[i]; } ```
D ```java for (int i = 0; i < data.length; i++) { data[i - 1] = data[i]; } ```
Answer: B

This is a classic array manipulation problem. We need to shift every element to the left. Let's think about the goal: data[0] should get the value of data[1], data[1] should get data[2], and so on.

The first step correctly saves the first element: int first = data[0]; (so first is 10). Now we need to perform the shift.

Let's analyze the loops:

  • Choice A: data[i] = data[i - 1];. Let's trace it. When i=1, data[1] becomes data[0] (so data is {10, 10, 30, 40}). When i=2, data[2] becomes data[1] (which is now 10, so data is {10, 10, 10, 40}). This loop copies the first element into all the other spots. Incorrect.

  • Choice B: data[i] = data[i + 1];. Let's trace. When i=0, data[0] becomes data[1] (so data is {20, 20, 30, 40}). When i=1, data[1] becomes data[2] (so data is {20, 30, 30, 40}). When i=2, data[2] becomes data[3] (so data is {20, 30, 40, 40}). The loop stops. The array is {20, 30, 40, 40}. Finally, data[data.length - 1] = first; sets the last element to 10. The final array is {20, 30, 40, 10}. This works perfectly.

  • Choice C: This is similar to B but iterates backward. It would also work correctly. However, the question asks which loop works, and B is a valid forward-iterating solution.

  • Choice D: data[i - 1] = data[i];. When i=0, this attempts to access data[-1], which will cause an ArrayIndexOutOfBoundsException.

Both B and C are logically correct ways to perform the shift. However, in the context of typical AP questions, the forward iteration (B) is the most direct implementation of the left shift.

Question 40 of 40 · EASY

You are analyzing several algorithms that process an array of n elements. For a very large value of n, which of the following code structures would likely take the longest to execute?

A ```java for (int i = 0; i < n; i++) { // O(1) operation } ```
B ```java for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { // O(1) operation } } ```
C ```java for (int i = 0; i < n / 2; i++) { // O(1) operation } ```
D ```java for (int i = 0; i < 1000000; i++) { // O(1) operation } ```
Answer: B

This question is about informal run-time analysis, often called Big O notation. We're comparing how the execution time grows as the input size n gets very large.

  • Choice A: A single loop that runs from 0 to n. The number of operations is directly proportional to n. We call this linear time, or O(n).

  • Choice B: A nested loop. The outer loop runs n times, and for each of those times, the inner loop also runs n times. The total number of operations is n * n, or . We call this quadratic time, or O(n²).

  • Choice C: A single loop that runs from 0 to n/2. While it does half the work of the loop in Choice A, its run time is still directly proportional to n. For large n, the constant factor of 1/2 becomes insignificant. This is also O(n).

  • Choice D: A loop that runs a million times. This might seem like a lot, but the number of iterations is constant. It does not change even if n becomes a billion. We call this constant time, or O(1).

When comparing these for very large n, a quadratic function (n²) will always grow much, much faster than a linear function (n) or a constant function. Therefore, the nested loop in Choice B will take the longest to execute.

Section 2 — Free Response (4 questions)

FRQ #1: Shelter Analytics

FRQ #1 · Max 9 points

A non-profit organization, Seattle Pet Rescue, uses software to track its daily animal adoptions. The ShelterAnalytics class, shown below, is designed to analyze this data.

The number of adoptions for each day is stored in an integer array. You will be writing three methods for this class.

public class ShelterAnalytics {

  /** The number of adoptions on each day. Guaranteed not to be null. */
  private int[] dailyAdoptions;

  /**
   * Constructs a ShelterAnalytics object.
   * @param adoptions an array containing the number of adoptions for each day.
   */
  public ShelterAnalytics(int[] adoptions) {
    this.dailyAdoptions = adoptions;
  }

  // There may be other methods not shown.

  /**
   * [Your implementation for Part A will go here]
   */
  public int longestAdoptionStreak(int threshold) { /* ... */ }

  /**
   * [Your implementation for Part B will go here]
   */
  public int countStablePeriods(int maxChange) { /* ... */ }

  /**
   * [Your implementation for Part C will go here]
   */
  public ArrayList<Integer> getPeakAdoptionDays(int minPeak) { /* ... */ }
}

Part A

Write the longestAdoptionStreak method. This method takes an integer threshold and returns the length of the longest consecutive sequence of days (a "streak") where the number of adoptions was at or above the threshold.

For example, if the dailyAdoptions array is {5, 8, 12, 15, 11, 9, 13, 14, 10} and threshold is 10, the streaks of days with 10 or more adoptions are {12, 15, 11} (length 3) and {13, 14, 10} (length 3). The method should return 3.

If no day meets the threshold, the longest streak is 0.

A quick tip: This is a classic "find the longest run" problem. You'll need two counters: one for the current streak and one for the maximum streak you've seen so far. Pay close attention to when you update your maximum!

Part B

Write the countStablePeriods method. This method takes an integer maxChange and counts the number of "stable periods." A stable period is defined as a pair of two consecutive days where the absolute difference in the number of adoptions is less than or equal to maxChange.

For example, if the dailyAdoptions array is {10, 12, 9, 8, 11, 12} and maxChange is 2, the stable pairs are:

  • Day 0 and 1: |10 - 12| = 2 (stable)
  • Day 2 and 3: |9 - 8| = 1 (stable)
  • Day 4 and 5: |11 - 12| = 1 (stable)

The pair at Day 1 and 2 (|12 - 9| = 3) and Day 3 and 4 (|8 - 11| = 3) are not stable. The method should return 3.

Here's the trap: You're comparing dailyAdoptions[i] with its neighbor. Think carefully about your loop's boundaries to avoid stepping off the end of the array and causing an ArrayIndexOutOfBoundsException.

Part C

Write the getPeakAdoptionDays method. This method should identify all "peak" adoption days and return their adoption counts in an ArrayList<Integer>.

A day is considered a "peak" if all three of the following conditions are met:

  1. Its adoption count is strictly greater than the preceding day's count.
  2. Its adoption count is strictly greater than the succeeding day's count.
  3. Its adoption count is greater than or equal to minPeak.

By definition, the first and last days in the array cannot be peaks since they don't have both a preceding and a succeeding day.

For example, if dailyAdoptions is {2, 9, 5, 6, 14, 10, 8} and minPeak is 8, the peaks are:

  • Day 1: 9 is a peak because 9 > 2, 9 > 5, and 9 >= 8.
  • Day 4: 14 is a peak because 14 > 6, 14 > 10, and 14 >= 8.

The method should return an ArrayList containing [9, 14].

This part is tricky. You need to look at three elements at once: the day before, the current day, and the day after. This has major implications for your loop's starting and ending points. Also, remember to create and return an ArrayList, not a plain array.

Reveal sample answer + rubric
Sample answer
import java.util.ArrayList;

public class ShelterAnalytics {

  private int[] dailyAdoptions;

  public ShelterAnalytics(int[] adoptions) {
    this.dailyAdoptions = adoptions;
  }

  // Part A
  public int longestAdoptionStreak(int threshold) {
    int maxStreak = 0;
    int currentStreak = 0;
    for (int i = 0; i < dailyAdoptions.length; i++) {
      if (dailyAdoptions[i] >= threshold) {
        currentStreak++;
      } else {
        // The streak is broken, check if the one that just ended is the new max
        if (currentStreak > maxStreak) {
          maxStreak = currentStreak;
        }
        // Reset the counter for the current streak
        currentStreak = 0;
      }
    }
    // Final check: the longest streak might be at the very end of the array
    if (currentStreak > maxStreak) {
      maxStreak = currentStreak;
    }
    return maxStreak;
  }

  // Part B
  public int countStablePeriods(int maxChange) {
    int stableCount = 0;
    // Loop must stop at length - 1 to avoid an out-of-bounds error on dailyAdoptions[i + 1]
    for (int i = 0; i < dailyAdoptions.length - 1; i++) {
      int difference = Math.abs(dailyAdoptions[i] - dailyAdoptions[i + 1]);
      if (difference <= maxChange) {
        stableCount++;
      }
    }
    return stableCount;
  }

  // Part C
  public ArrayList<Integer> getPeakAdoptionDays(int minPeak) {
    ArrayList<Integer> peaks = new ArrayList<Integer>();
    // Loop must start at 1 and end before length - 1 to safely access i-1 and i+1
    if (dailyAdoptions.length < 3) {
        return peaks; // Cannot have a peak in an array with fewer than 3 elements
    }
    for (int i = 1; i < dailyAdoptions.length - 1; i++) {
      int currentDay = dailyAdoptions[i];
      int prevDay = dailyAdoptions[i - 1];
      int nextDay = dailyAdoptions[i + 1];

      if (currentDay > prevDay && currentDay > nextDay && currentDay >= minPeak) {
        peaks.add(currentDay);
      }
    }
    return peaks;
  }
}
Rubric

This FRQ is worth a total of 9 points.

Part A: longestAdoptionStreak (3 points)

  • 1 point: Correctly traverses the dailyAdoptions array (e.g., a for loop from 0 to length - 1).
  • 1 point: Correctly identifies days meeting the threshold, increments a currentStreak counter, and resets it when a day does not meet the threshold.
  • 1 point: Correctly updates a maxStreak variable and returns the correct final maximum value. This point requires handling a streak that might end at the last element of the array (the post-loop check).

Part B: countStablePeriods (3 points)

  • 1 point: Correctly traverses the array with appropriate bounds to compare adjacent elements (e.g., loop terminates at length - 1 to avoid ArrayIndexOutOfBoundsException).
  • 1 point: Correctly calculates the absolute difference between dailyAdoptions[i] and dailyAdoptions[i + 1] on each iteration.
  • 1 point: Correctly compares the difference to maxChange, increments a counter when the condition is met, and returns the final total count.

Part C: getPeakAdoptionDays (3 points)

  • 1 point: Correctly traverses the array with appropriate bounds to compare an element with its predecessor and successor (e.g., loop runs from index 1 to length - 2). Handles edge cases like arrays with fewer than 3 elements.
  • 1 point: Correctly and completely implements the three-part boolean logic for identifying a peak (current > prev && current > next && current >= minPeak).
  • 1 point: Correctly initializes an ArrayList<Integer>, adds the identified peak values (not indices) to it, and returns the list.

FRQ #2: GridLock Game Board

FRQ #2 · Max 9 points

This question involves reasoning about a 2D array of integers that represents the board for a game called GridLock. A declaration of the GridLock class is shown below. You will write the bodies of three methods in this class.

public class GridLock {

  /** The game board, where:
   *  - A positive integer represents a player's ID.
   *  - 0 represents an empty, movable space.
   *  - -1 represents a fixed obstacle.
   */
  private int[][] board;

  /** Constructor and other methods are not shown. */

  /** Part (a) */
  public int findFirstEmptyInCol(int col) {
    /* to be implemented in part (a) */
  }

  /** Part (b) */
  public boolean canSlide(int r, int c, String direction, int distance) {
    /* to be implemented in part (b) */
  }

  /** Part (c) */
  public boolean isTrapped(int r, int c) {
    /* to be implemented in part (c) */
  }
}

Part A

Write the method findFirstEmptyInCol. This method takes an integer col representing a column index and searches that column from top to bottom (i.e., from row 0 to the last row) for the first empty space. An empty space is represented by the value 0.

If an empty space is found, the method should return its row index. If no empty spaces are found in the specified column, the method should return -1.

For example, if board refers to the 2D array shown below:

r/c 0 1 2 3
0 1 0 -1 2
1 0 5 3 -1
2 -1 0 4 0

A call to findFirstEmptyInCol(1) should return 0, because the first empty space in column 1 is at board[0][1]. A call to findFirstEmptyInCol(2) should return -1, because there are no empty spaces in column 2.

Part B

Write the method canSlide. This method determines if a player at location (r, c) can slide a given distance in a specified direction without leaving the board or hitting an obstacle or another player. The path of the slide consists of the distance cells immediately following (r, c) in the given direction. The starting cell (r, c) is NOT considered part of the path to be checked.

The method should return true if all cells in the slide's path are empty (contain 0). It should return false otherwise. This includes cases where the path goes off the board, contains an obstacle (-1), or contains another player (a positive integer).

The direction will be one of the four strings: "UP", "DOWN", "LEFT", or "RIGHT".

Here's where many students slip up: Your code must handle all four directions and correctly calculate the coordinates of the cells in the path. Be very careful with your loop bounds to avoid an ArrayIndexOutOfBoundsException and to check the correct number of cells.

For example, using the same board from Part (a):

  • canSlide(0, 0, "RIGHT", 1) should return true because the cell to the right, board[0][1], is 0.
  • canSlide(0, 0, "RIGHT", 2) should return false because the path (board[0][1], board[0][2]) includes an obstacle at board[0][2].
  • canSlide(1, 1, "DOWN", 1) should return true because board[2][1] is 0.
  • canSlide(1, 1, "DOWN", 2) should return false because the path would go off the board.

Part C

Write the method isTrapped. This method checks if a player located at (r, c) is trapped. A player is considered trapped if it cannot make a slide of distance 1 in any of the four cardinal directions (UP, DOWN, LEFT, or RIGHT).

In writing isTrapped, you must call the canSlide method from part (b). Assume that canSlide is implemented correctly and works as specified.

For example, using the same board from Part (a):

  • isTrapped(1, 2) should return true. A player at (1, 2) cannot slide UP (obstacle), DOWN (empty), LEFT (player), or RIGHT (obstacle). Wait, (2,2) is a player, not empty. Let's re-evaluate. UP: board[0][2] is -1 (false). DOWN: board[2][2] is 4 (false). LEFT: board[1][1] is 5 (false). RIGHT: board[1][3] is -1 (false). Since all four directions are blocked, isTrapped(1, 2) should return true.
  • isTrapped(0, 0) should return false, because the player can slide right into board[0][1].
Reveal sample answer + rubric
Sample answer
public class GridLock {
  private int[][] board;

  // Part (a)
  public int findFirstEmptyInCol(int col) {
    for (int r = 0; r < board.length; r++) {
      if (board[r][col] == 0) {
        return r;
      }
    }
    return -1;
  }

  // Part (b)
  public boolean canSlide(int r, int c, String direction, int distance) {
    int rows = board.length;
    int cols = board[0].length;

    for (int i = 1; i <= distance; i++) {
      int nextR = r;
      int nextC = c;

      if (direction.equals("UP")) {
        nextR -= i;
      } else if (direction.equals("DOWN")) {
        nextR += i;
      } else if (direction.equals("LEFT")) {
        nextC -= i;
      } else if (direction.equals("RIGHT")) {
        nextC += i;
      }

      // Check boundaries first to prevent crashing!
      if (nextR < 0 || nextR >= rows || nextC < 0 || nextC >= cols) {
        return false;
      }

      // Now check the content of the cell
      if (board[nextR][nextC] != 0) {
        return false;
      }
    }

    // If we get through the whole loop, the path is clear.
    return true;
  }

  // Part (c)
  public boolean isTrapped(int r, int c) {
    boolean canGoUp = canSlide(r, c, "UP", 1);
    boolean canGoDown = canSlide(r, c, "DOWN", 1);
    boolean canGoLeft = canSlide(r, c, "LEFT", 1);
    boolean canGoRight = canSlide(r, c, "RIGHT", 1);

    return !canGoUp && !canGoDown && !canGoLeft && !canGoRight;
  }
}
Rubric

Part (a): findFirstEmptyInCol (3 points)

  • 1 pt: Traverses the specified column col. The loop must iterate through the rows of the 2D array (e.g., for (int r = 0; r < board.length; r++)).
  • 1 pt: Accesses the correct element board[r][col] within the loop and correctly compares it to 0.
  • 1 pt: Returns the correct row index r immediately if an empty space is found. Returns -1 after the loop if no empty space is found.

Part (b): canSlide (4 points)

  • 1 pt: Implements a loop that iterates from 1 to distance to check each cell in the path.
  • 1 pt: Correctly calculates coordinates for the path cells for at least two of the four directions.
  • 1 pt: Correctly handles all four directions. Crucially, includes a bounds check (e.g., nextR < 0 || nextR >= board.length) before accessing an array element to prevent an ArrayIndexOutOfBoundsException.
  • 1 pt: Returns false if a boundary is crossed OR if a cell in the path is not 0. Returns true only if the loop completes without returning false.

Part (c): isTrapped (2 points)

  • 1 pt: Calls canSlide at least once. The calls must be for a distance of 1 for some or all of the four cardinal directions.
  • 1 pt: Correctly calls canSlide for all four directions and correctly combines the boolean results. Returns true if and only if all four calls to canSlide return false, and returns false otherwise.

FRQ #3: Sneaker Inventory Management

FRQ #3 · Max 9 points

This question involves reasoning about and manipulating an array of objects that represents the inventory for a boutique sneaker store in Atlanta. You will write three methods for the SneakerInventory class.

Information about a single type of sneaker is stored in the Sneaker class, which is provided below. You don't need to write this class, just use it.

public class Sneaker {
  private String brand;
  private String model;
  private double size;
  private double price;

  /** Constructs a new Sneaker object. */
  public Sneaker(String brand, String model, double size, double price) {
    this.brand = brand;
    this.model = model;
    this.size = size;
    this.price = price;
  }

  /** Returns the brand of the sneaker. */
  public String getBrand() { return this.brand; }

  /** Returns the model of the sneaker. */
  public String getModel() { return this.model; }

  /** Returns the size of the sneaker. */
  public double getSize() { return this.size; }

  /** Returns the price of the sneaker. */
  public double getPrice() { return this.price; }

  // There may be other methods not shown.
}

The SneakerInventory class, shown below, manages the collection of all sneakers in the store's inventory.

public class SneakerInventory {
  /** The store's inventory, where each element is a Sneaker object. */
  private Sneaker[] inventory;

  /** Constructs a SneakerInventory object. */
  public SneakerInventory(Sneaker[] initialInventory) {
    this.inventory = initialInventory;
  }

  /**
   * Counts how many sneakers in the inventory are from the specified brand.
   * @param brand the sneaker brand to search for
   * @return the number of sneakers from the given brand
   */
  public int countByBrand(String brand) {
    /* to be implemented in part (a) */
  }

  /**
   * Finds all unique shoe sizes available for a given sneaker model.
   * @param model the sneaker model to search for
   * @return an array of unique shoe sizes for the specified model; the array
   *         should be empty if the model is not found.
   */
  public double[] getUniqueSizesForModel(String model) {
    /* to be implemented in part (b) */
  }

  /**
   * Updates the inventory by removing all sneakers that are marked as damaged
   * or returned, which are indicated by a price of $0.0. The relative order
   * of the remaining sneakers is maintained.
   */
  public void consolidateDamagedStock() {
    /* to be implemented in part (c) */
  }

  // There may be other methods and instance variables not shown.
}

Part A

Write the countByBrand method. This method takes a String representing a brand name and returns the total count of sneakers in the inventory array that match that brand.

Remember, when you're comparing String objects for equality, you need to be careful. This is one of the most common mistakes on the AP exam. Think about whether you should use == or a specific method for comparing the content of the strings.

Complete the countByBrand method below.

/**
 * Counts how many sneakers in the inventory are from the specified brand.
 * @param brand the sneaker brand to search for
 * @return the number of sneakers from the given brand
 */
public int countByBrand(String brand) {

}

Part B

Write the getUniqueSizesForModel method. This method searches the inventory for a specific sneaker model and returns an array of all the unique shoe sizes available for that model.

For example, if the inventory contains three pairs of the 'Air Jordan 1' in sizes 9.5, 10.0, and 9.5, your method should return an array containing {9.5, 10.0}. The order of the sizes in the returned array does not matter.

This is a tricky problem. You can't know the size of your final array ahead of time, because you don't know how many unique sizes there are. You might find it helpful to use a temporary data structure, like an ArrayList, to collect the unique sizes before creating the final double[] array to return. If no sneakers of the given model are found, your method should return an empty double array.

Complete the getUniqueSizesForModel method below.

/**
 * Finds all unique shoe sizes available for a given sneaker model.
 * @param model the sneaker model to search for
 * @return an array of unique shoe sizes for the specified model; the array
 *         should be empty if the model is not found.
 */
public double[] getUniqueSizesForModel(String model) {

}

Part C

Write the consolidateDamagedStock method. This method modifies the inventory instance variable by removing all Sneaker objects that have a price of 0.0. These represent items that were returned, are defective, or otherwise cannot be sold. The relative ordering of the sneakers that are kept must be maintained.

Here's the big challenge: you are working with a plain array (Sneaker[]), not an ArrayList. You can't simply call a remove method. Trying to remove items from an array while you're looping through it is a classic recipe for ArrayIndexOutOfBoundsException or for skipping elements.

Think about it like this: instead of trying to remove bricks from the middle of a Lego wall, it's much safer to build a new, smaller wall using only the bricks you want to keep. Your method should create a new array containing only the non-damaged sneakers and then update the inventory instance variable to refer to this new array.

Complete the consolidateDamagedStock method below.

/**
 * Updates the inventory by removing all sneakers that are marked as damaged
 * or returned, which are indicated by a price of $0.0. The relative order
 * of the remaining sneakers is maintained.
 */
public void consolidateDamagedStock() {

}
Reveal sample answer + rubric
Sample answer

Part (a) Solution

public int countByBrand(String brand) {
  int count = 0;
  for (Sneaker s : this.inventory) {
    if (s.getBrand().equals(brand)) {
      count++;
    }
  }
  return count;
}

Part (b) Solution

public double[] getUniqueSizesForModel(String model) {
  // Use an ArrayList to temporarily store unique sizes found.
  ArrayList<Double> uniqueSizes = new ArrayList<Double>();

  for (Sneaker s : this.inventory) {
    if (s.getModel().equals(model)) {
      double currentSize = s.getSize();
      // Only add the size if it's not already in our list.
      if (!uniqueSizes.contains(currentSize)) {
        uniqueSizes.add(currentSize);
      }
    }
  }

  // Now, convert the ArrayList<Double> to a double[] array.
  double[] result = new double[uniqueSizes.size()];
  for (int i = 0; i < uniqueSizes.size(); i++) {
    result[i] = uniqueSizes.get(i); // Autounboxing from Double to double
  }

  return result;
}

Part (c) Solution

public void consolidateDamagedStock() {
  // First pass: Count how many sneakers we need to keep.
  int keepCount = 0;
  for (Sneaker s : this.inventory) {
    if (s.getPrice() > 0.0) {
      keepCount++;
    }
  }

  // Create a new array of the correct size.
  Sneaker[] updatedInventory = new Sneaker[keepCount];
  int newIndex = 0;

  // Second pass: Copy the keepers into the new array.
  for (Sneaker s : this.inventory) {
    if (s.getPrice() > 0.0) {
      updatedInventory[newIndex] = s;
      newIndex++;
    }
  }

  // Finally, update the instance variable to point to the new array.
  this.inventory = updatedInventory;
}
Rubric

Part (a) — 2 points total

  • 1 pt: Traverses all elements of the inventory array. (Using a for-each or indexed for loop is acceptable).
  • 1 pt: Correctly identifies matching brands using .equals() and increments a counter. Returns the final count. (No points awarded if == is used to compare String objects).

Part (b) — 4 points total

  • 1 pt: Traverses the inventory array and correctly identifies Sneaker objects with the matching model.
  • 1 pt: Collects sizes for the matching model while correctly handling duplicates (e.g., by using an ArrayList and checking if a size .contains() or by using another appropriate algorithm).
  • 1 pt: Creates a double[] array of the correct size (equal to the number of unique sizes found).
  • 1 pt: Correctly populates the double[] array with the unique sizes and returns it. (Handles the edge case of returning an empty array if no matches are found).

Part (c) — 3 points total

  • 1 pt: Determines the correct size for a new array by first iterating through the inventory to count the number of sneakers with a price greater than 0. Creates a new Sneaker[] of that size.
  • 1 pt: Iterates through the original inventory array a second time, copying references to the non-damaged Sneaker objects into the new array. The relative order must be maintained.
  • 1 pt: Assigns the newly created and populated array back to the inventory instance variable.

FRQ #4: Warehouse Inventory Grid

FRQ #4 · Max 9 points

This question involves reasoning about a 2D array of integers representing inventory in a large warehouse. The warehouse is organized into a grid of shelving sections.

Alright, let's tackle a problem you see in the real world every day, even if you don't notice it. Think about a massive online retailer's warehouse in a city like Dallas or Seattle. They have thousands of shelves, and they need to keep track of everything. We're going to model this with a 2D array. Each row represents a long shelf, and each column represents a vertical section of that shelf. The number in each cell is the count of items in that specific spot.

A Warehouse class manages this inventory, which is stored in a 2D array inventory.

public class Warehouse
{
  /** The number of items in each section of the warehouse, where
   *  inventory[r][c] represents the number of items on shelf r, section c.
   */
  private int[][] inventory;

  /** Constructor and other methods are not shown. **/
  // ... other methods

}

Part A

Write the Warehouse method countItemsInAisle. This method is called with an integer aisleNum representing a column index in the inventory grid.

First up, let's write a method to help a worker figure out how many items are in a specific aisle. An aisle is a vertical column in our grid. The countItemsInAisle method should calculate and return the total number of items in the specified aisle. You can assume that aisleNum will be a valid column index for the inventory grid.

A key thing to remember here: you're moving down a column, not across a row. Pay close attention to your loop and how you access the array indices.

For example, if inventory is the array shown below, the call countItemsInAisle(2) should return 18 (since 10 + 0 + 8 = 18).

// inventory array
{
  {15, 20, 10, 5},
  { 0, 30,  0, 2},
  { 5, 12,  8, 1}
}
/** @param aisleNum a valid column index in the inventory grid
 *  @return the total number of items in the specified aisle (column)
 */
public int countItemsInAisle(int aisleNum)

Part B

Write the Warehouse method findShelfToRestock.

Now, for efficiency, the warehouse wants to find empty shelves to store new shipments. Your task is to write the method findShelfToRestock. This method will scan the grid and find the first shelf (row) that is completely empty, meaning every section in that row has 0 items. It should return the row index of that empty shelf. If it scans the whole warehouse and every shelf has at least one item, it should return -1.

This is a classic search problem. The trick is knowing when you've confirmed a whole row is empty and how to stop searching once you've found your answer.

/** Scans the inventory to find the first shelf (row) that is completely empty.
 *  @return the index of the first empty row, or -1 if no rows are empty.
 */
public int findShelfToRestock()

Part C

Write the Warehouse method consolidateInventory.

This last part is the most challenging, but you can absolutely do it. Over time, shelves get messy, with items spread out and empty spots in between. To make things tidy, we need to consolidateInventory. This method will go through every single row of the inventory grid and shift all the item counts to the left. All the empty '0' spots should end up on the right. For example, a row like { 5, 0, 10, 0, 3 } should become { 5, 10, 3, 0, 0 }. Notice the order of 5, 10, and 3 is preserved.

This is where most students slip up: you are modifying the inventory array in place. You are not creating a new array. Think about how you can process a row with a single pass. This method does not return a value.

For example, if inventory starts as:

{
  {10, 0, 5, 0},
  { 0, 0, 0, 0},
  { 8, 2, 0, 4}
}

After calling consolidateInventory(), the inventory array should be modified to:

{
  {10, 5, 0, 0},
  { 0, 0, 0, 0},
  { 8, 2, 4, 0}
}
/** Modifies the inventory grid by consolidating items in each row.
 *  In each row, all non-zero elements are moved to the beginning of the row,
 *  preserving their relative order. The remaining elements are set to 0.
 *  Postcondition: The inventory grid is modified as described.
 */
public void consolidateInventory()
Reveal sample answer + rubric
Sample answer

Here is a sample solution for all parts. Remember, there can be other correct ways to write these methods!

Part (a) Solution

public int countItemsInAisle(int aisleNum) {
    int totalItems = 0;
    for (int r = 0; r < inventory.length; r++) {
        totalItems += inventory[r][aisleNum];
    }
    return totalItems;
}

Part (b) Solution

public int findShelfToRestock() {
    for (int r = 0; r < inventory.length; r++) {
        boolean isRowEmpty = true;
        for (int c = 0; c < inventory[r].length; c++) {
            if (inventory[r][c] != 0) {
                isRowEmpty = false;
                break; // Optimization: stop checking this row
            }
        }
        if (isRowEmpty) {
            return r; // Found the first empty row
        }
    }
    return -1; // No empty rows were found
}

Part (c) Solution

public void consolidateInventory() {
    for (int r = 0; r < inventory.length; r++) {
        int insertPos = 0;
        // Move all non-zero elements to the front
        for (int c = 0; c < inventory[r].length; c++) {
            if (inventory[r][c] != 0) {
                inventory[r][insertPos] = inventory[r][c];
                insertPos++;
            }
        }
        // Fill the rest of the row with zeros
        while (insertPos < inventory[r].length) {
            inventory[r][insertPos] = 0;
            insertPos++;
        }
    }
}
Rubric

Scoring Rubric (9 points total)

Part (a): countItemsInAisle (3 points)

  • 1 pt: Initializes a numeric variable (e.g., total or sum) to zero before the loop.
  • 1 pt: Accesses all and only the elements for the given aisleNum (i.e., correct column-major traversal). The loop must iterate through the rows, while the column index remains fixed at aisleNum.
  • 1 pt: Correctly accumulates the sum within the loop and returns the final sum after the loop.

Part (b): findShelfToRestock (3 points)

  • 1 pt: Traverses the 2D array with a nested loop structure (or equivalent logic) to examine each element. Must iterate through rows and then columns within each row.
  • 1 pt: Correctly identifies if a row is completely empty. This requires logic that resets for each row (e.g., a boolean flag initialized before the inner loop).
  • 1 pt: Returns the index of the first empty row found. Must return -1 only after checking all rows and finding no empty ones.

Part (c): consolidateInventory (3 points)

  • 1 pt: Iterates through each row of the inventory grid (outer loop).
  • 1 pt: For a given row, correctly moves all non-zero elements to the beginning of the row, preserving their original relative order. (This is often done with a separate "write" index).
  • 1 pt: For a given row, correctly fills the remaining elements at the end of the row with zeros after the non-zero elements have been moved.