FRQ
› csapa
WordChecker: 2024 FRQ 3
A step-by-step solution to the 2024 AP CSA FRQ 3 (WordChecker), covering checking a chain of substrings across an ArrayList and filtering it into a new one with prefixes stripped in Java.
Checking whether a list of words forms an unbroken "chain" — each one hiding the last inside it — is the first half of this AP Computer Science A free-response question, followed by pulling out just the words that start a certain way and trimming that starting piece off.
What This FRQ Tests
- AP CSA units: Unit 6 (Array) and Unit 7 (ArrayList)
- Core skill: comparing each element of an
ArrayListto the one before it - Secondary skill: filtering an
ArrayListinto a new one while transforming each kept element along the way - Official category: "Array/ArrayList" — always FRQ 3 on the AP CSA exam
The Setup
WordCheckerholds one field:private ArrayList<String> wordList— guaranteed non-null, with nonullentries.- You're asked to write two methods:
boolean isWordChain()— checks whether every element (after the first) contains the one before it as a substringArrayList<String> createList(String target)— collects every element that starts withtarget, with that leadingtargetstripped off
Part (a): Writing isWordChain()
The Rule, Broken Down
- Compare every element starting from index
1to the element right before it. - Each of those elements must contain the previous one somewhere inside it (as a substring).
- If even one comparison fails, the whole thing isn't a chain.
Step-by-Step Approach
- Loop
ifrom1up to (not including)wordList.size(). - At each
i, grab the current element and the one right before it. - Check whether the current element contains the previous one using
indexOf. - The moment one comparison fails, return
falseimmediately. - If the loop finishes without ever failing, return
true.
The Code
public boolean isWordChain()
{
for (int i = 1; i < wordList.size(); i++)
{
String previous = wordList.get(i - 1);
String current = wordList.get(i);
if (current.indexOf(previous) == -1)
{
return false;
}
}
return true;
}
Why Each Piece Matters
- The loop starts at
i = 1, not0— the first element has nothing before it to compare against, so the rule only applies from the second element onward. current.indexOf(previous) == -1—indexOfreturns-1exactly when the argument never appears anywhere inside the string it's called on, which is precisely "does not contain as a substring."- Returning
falsethe instant a comparison fails — the whole chain is broken by a single failure, so there's no reason to keep checking the rest once one has already failed. - Returning
trueonly after the loop finishes — this correctly handles the case where every single comparison passed.
Tracing the Example
Using both examples from the question:
wordList |
Comparison | Result |
|---|---|---|
["an", "band", "band", "abandon"] |
"band" contains "an"? yes |
continue |
"band" contains "band"? yes |
continue | |
"abandon" contains "band"? yes |
continue | |
| (loop finishes) | returns true — matches |
|
["to", "too", "stool", "tools"] |
"too" contains "to"? yes |
continue |
"stool" contains "too"? yes |
continue | |
"tools" contains "stool"? no |
returns false — matches |
Common Mistakes to Avoid
- Starting the loop at
i = 0and trying to compare the first element to something before it, which doesn't exist. - Checking
previous.indexOf(current)instead ofcurrent.indexOf(previous)— the rule is specifically that each element contains the previous one, not the other way around. - Continuing to loop and overwrite the result after a failure is found, instead of returning immediately — this risks a later, passing comparison masking an earlier failure.
Part (b): Writing createList(String target)
The Rule, Broken Down
- Look at every element of
wordList. - Keep only the ones that start with
target. - For each one kept, strip off that leading occurrence of
targetbefore adding it to the result. - Keep the original relative order.
Step-by-Step Approach
- Create an empty
ArrayList<String>to collect results. - Loop over every element of
wordList. - Check whether that element is at least as long as
targetand its firsttarget.length()characters equaltarget. - If so, add the remainder of the string (everything after that prefix) to the result.
- After the loop, return the result.
The Code
public ArrayList<String> createList(String target)
{
ArrayList<String> result = new ArrayList<String>();
for (int i = 0; i < wordList.size(); i++)
{
String word = wordList.get(i);
if (word.length() >= target.length() && word.substring(0, target.length()).equals(target))
{
result.add(word.substring(target.length()));
}
}
return result;
}
Why Each Piece Matters
word.length() >= target.length(), checked first — this guards against callingsubstring(0, target.length())on a word shorter thantarget, which would throw aStringIndexOutOfBoundsException. Java's&&short-circuits, so thesubstringcall never runs unless the length check already passed.word.substring(0, target.length()).equals(target)— pulls out exactly the firsttarget.length()characters and compares them for an exact prefix match.word.substring(target.length())— the one-argument form ofsubstringreturns everything from that index to the end, which is exactly "the word with the leadingtargetremoved."
Tracing the Example
Using wordList = ["catch", "bobcat", "catchacat", "cat", "at"] and createList("cat") (target length 3):
| Word | Length ≥ 3? | Starts with "cat"? |
Added |
|---|---|---|---|
"catch" |
yes | yes | "ch" |
"bobcat" |
yes | no | (skipped) |
"catchacat" |
yes | yes | "chacat" |
"cat" |
yes | yes | "" |
"at" |
no (length 2) | (skipped before checking) | (skipped) |
Result: ["ch", "chacat", ""] — matches the question's expected output exactly, including the empty string for "cat" matching itself completely.
Common Mistakes to Avoid
- Skipping the length check entirely. Calling
substring(0, target.length())on a word shorter thantarget(like"at"against"cat") crashes with an exception rather than just failing the comparison. - Using
contains-style logic instead of a prefix check. The rule is specifically "starts with," not "contains anywhere" —"bobcat"does contain"cat", but not at the start, so it's correctly excluded. - Forgetting to strip the prefix before adding to the result — the returned list holds the remainder of each matching word, not the original word.
Key Takeaways
- Comparing "this element to the previous one" always starts the loop at index
1, since the first element has nothing to compare against. indexOf(x) == -1is the standard "does not contain" check; a prefix check instead usessubstring(0, x.length()).equals(x), guarded by a length check first.- Filtering into a new
ArrayListwhile transforming each kept element is a three-step pattern: check a condition, compute the transformed value, add it — all inside the same loop.