Free-Response Practice

FRQ Practice

The AP CSA exam is half free-response — four problems, 90 minutes, 36 points total. Practice all four types here, write your code in the built-in IDE, then check yourself against the official rubric style.

The Exam Format

FRQTypeTypically TestsPoints
1MethodsMethod writing, control flow, Strings9
2ClassDesigning & writing a class9
3Array/ArrayList1D collection algorithms9
42D Array2D array traversal9

Total: 90 minutes for 36 points. That's about 22 minutes per question. Don't spend more.

Pacing

Spend 5 minutes reading and 17 writing per FRQ. Don't get stuck — skip ahead and come back. Partial credit is real.

Compile-ability

Your code must compile to earn full credit. Curly braces, semicolons, return types, parameter types — all required.

📋

Use the signature

Copy the method header from the prompt exactly. Don't invent your own parameter names — they should match what the problem gave you.

🔧

Use helpers

If the problem gives you a method to use (an accessor, a helper), USE IT. Re-implementing the helper costs you a rubric point.

💡 The single best FRQ tip

The rubric is based on behavior, not style. As long as your code does the right thing for the listed test cases, you get the points. Elegant code with a bug scores worse than ugly code that works.

Methods 9 points ~22 min

Practice FRQ 1A — String Hider

A code key is a string of letters used to encode a message. The StringHider class has a static method hide that takes a String message and replaces every letter that also appears in a given key with the character '*'. All other characters (digits, spaces, punctuation, letters not in the key) remain unchanged. Comparison is case-sensitive.

Write the method hide with the following signature:

public static String hide(String message, String key)

Examples:

CallReturns
hide("Hello World", "lo")"He*** W*r*d"
hide("Java rocks!", "aeiou")"J*v* r*cks!"
hide("abc 123", "xyz")"abc 123"

Write your attempt below, then run it. The main method calls the test cases.

📊 Show Rubric (9 points)
Traverses every character of message using a loop+2
Correctly extracts each character (e.g. substring(i, i+1))+1
Uses indexOf or substring search to check key membership+2
Appends "*" when the character is in the key+1
Appends the original character when not in the key+1
Returns the built String+1
Compiles & produces correct output on the three sample inputs+1
💡 Show Solution Walkthrough

Strategy: Build the answer one character at a time. For each character, check if it's in the key — if yes, append *; if not, append the character itself.

public static String hide(String message, String key) {
    String result = "";
    for (int i = 0; i < message.length(); i++) {
        String ch = message.substring(i, i + 1);
        if (key.indexOf(ch) >= 0) {
            result += "*";
        } else {
            result += ch;
        }
    }
    return result;
}

Why this scores 9/9: Walks every char, uses indexOf for key check (returns –1 when not found), builds result correctly, handles edge cases (empty key works too).

Methods 9 points

Practice FRQ 1B — Digit Sum

Write a static method digitSum(int n) that returns the sum of all the digits in a non-negative integer n. Example: digitSum(1729) = 1+7+2+9 = 19. Precondition: n >= 0.

Then write a static method isDigitMultiple(int n) that returns true if and only if the digit sum of n divides n evenly. Use your digitSum helper. Example: isDigitMultiple(18) is true because digit sum is 9 and 18 / 9 = 2 with no remainder.

📊 Show Rubric (9 points)
digitSum: handles n = 0 correctly (returns 0)+1
digitSum: loop continues while there are digits left+1
digitSum: extracts last digit with n % 10+1
digitSum: removes last digit with n / 10+1
digitSum: accumulates and returns sum+2
isDigitMultiple: calls digitSum rather than re-implementing+1
isDigitMultiple: uses % to check divisibility correctly+1
isDigitMultiple: handles the edge case where digit sum is 0 (n must be 0)+1
💡 Show Solution
public static int digitSum(int n) {
    if (n == 0) return 0;
    int sum = 0;
    while (n > 0) {
        sum += n % 10;
        n = n / 10;
    }
    return sum;
}

public static boolean isDigitMultiple(int n) {
    int s = digitSum(n);
    if (s == 0) return n == 0;   // avoid divide by zero
    return n % s == 0;
}
Class 9 points

Practice FRQ 2 — Locker

A school assigns lockers to students. Implement a Locker class with these specifications:

  • Each locker has a number (int, immutable after construction) and an owner name (String, may change).
  • The constructor takes a locker number; the locker initially has no owner (use an empty String).
  • Accessors: getNumber(), getOwner().
  • assign(String name) sets the owner if the locker is currently unassigned (owner is the empty String). Returns true on success, false if already assigned.
  • release() clears the owner (sets to empty String). No return value.
  • isAvailable() returns true if the locker has no owner.
📊 Show Rubric (9 points)
Two private instance variables: number (int) and owner (String)+2
Constructor sets number; sets owner to empty String+1
getNumber and getOwner return the right fields+1
isAvailable returns whether owner is empty (uses .equals()!)+1
assign checks availability before assigning+1
assign returns true on success, false on failure+1
assign actually updates the owner field+1
release clears owner to empty String+1
💡 Show Solution
class Locker {
    private int number;
    private String owner;

    public Locker(int number) {
        this.number = number;
        owner = "";
    }

    public int getNumber()    { return number; }
    public String getOwner()  { return owner;  }
    public boolean isAvailable() { return owner.equals(""); }

    public boolean assign(String name) {
        if (!isAvailable()) return false;
        owner = name;
        return true;
    }

    public void release() { owner = ""; }
}

Common mistakes: Using == instead of .equals() for the empty String check, forgetting private on instance variables, having assign always update (even when the locker is taken).

ArrayList 9 points

Practice FRQ 3 — Score Filter

Given an ArrayList<Integer> of test scores, write two static methods:

  • countPassing(ArrayList<Integer> scores, int min) returns the count of scores ≥ min.
  • removeFailing(ArrayList<Integer> scores, int min) removes every score below min from the list in place. Returns nothing.
📊 Show Rubric (9 points)
countPassing: loop traverses entire list+1
countPassing: condition >= min (not just >)+1
countPassing: increments counter and returns it+2
removeFailing: iterates correctly without skipping elements after removal+2
removeFailing: uses list.remove(i) to modify in place+1
removeFailing: condition is "below min" (i.e., < min)+1
Compiles and produces correct output on the sample+1
💡 Show Solution
public static int countPassing(ArrayList<Integer> scores, int min) {
    int count = 0;
    for (int s : scores) {
        if (s >= min) count++;
    }
    return count;
}

public static void removeFailing(ArrayList<Integer> scores, int min) {
    // Iterate BACKWARDS to avoid index-shifting bugs
    for (int i = scores.size() - 1; i >= 0; i--) {
        if (scores.get(i) < min) scores.remove(i);
    }
}

Common bug: Iterating forward with remove. After you remove index i, the next element shifts into i — but your loop increments to i+1, skipping it. Iterate backwards.

2D Array 9 points

Practice FRQ 4 — Seating Chart

A classroom is modeled as a rectangular 2D array of String where each cell contains a student name or "" for an empty seat. Write three static methods:

  • countEmpty(String[][] room) returns the number of empty seats.
  • rowWithMostStudents(String[][] room) returns the index of the row with the most occupied seats. If there's a tie, return the smallest index.
  • compactRow(String[][] room, int r) rearranges row r so all occupied seats come first, in their original order, and all empty seats follow.
📊 Show Rubric (9 points)
countEmpty: nested loop over rows and columns+1
countEmpty: correctly tests for empty using .equals("")+1
countEmpty: increments and returns the count+1
rowWithMostStudents: counts occupied per row+1
rowWithMostStudents: tracks best count and best row, returns it+1
rowWithMostStudents: tie-breaks toward smallest index (uses >, not >=)+1
compactRow: gathers non-empty names in order+1
compactRow: writes them back to the start of row r+1
compactRow: fills the rest of row r with ""+1
💡 Show Solution
public static int countEmpty(String[][] room) {
    int count = 0;
    for (int r = 0; r < room.length; r++) {
        for (int c = 0; c < room[r].length; c++) {
            if (room[r][c].equals("")) count++;
        }
    }
    return count;
}

public static int rowWithMostStudents(String[][] room) {
    int bestRow = 0, bestCount = -1;
    for (int r = 0; r < room.length; r++) {
        int count = 0;
        for (int c = 0; c < room[r].length; c++) {
            if (!room[r][c].equals("")) count++;
        }
        if (count > bestCount) {     // strictly greater → ties go to smaller index
            bestCount = count;
            bestRow   = r;
        }
    }
    return bestRow;
}

public static void compactRow(String[][] room, int r) {
    int writeIdx = 0;
    String[] row = room[r];
    // Pass 1: collect non-empties at the start
    for (int c = 0; c < row.length; c++) {
        if (!row[c].equals("")) {
            row[writeIdx++] = row[c];
        }
    }
    // Pass 2: fill the rest with ""
    while (writeIdx < row.length) row[writeIdx++] = "";
}

The two-pointer trick: A write index tracks where to put the next non-empty. After scanning, fill the tail with empties. This is the standard "compact" pattern.

Want more practice?

The College Board publishes every past FRQ with scoring guidelines.

Past Exam Questions (College Board)