HorseBarn: 2012 FRQ 3
A step-by-step solution to the 2012 AP CSA FRQ 3 (HorseBarn), covering linear search through an array of interface references and compacting an array in place while preserving order in Java.
Managing numbered stalls in a horse barn gives this AP Computer Science A free-response question its shape — one method searches a plain array for a horse by name, and the other squeezes every gap out of that same array without disturbing the order anyone's left in.
What This FRQ Tests
- AP CSA units: Unit 6 (Array), Unit 3 (Boolean Expressions and if Statements), and Unit 4 (Iteration)
- Core skill: a linear search through an array that has to skip over empty (
null) slots - Secondary skill: shifting elements within an array in place to remove gaps, while keeping every remaining element in its original relative order
- Official category: "Array/ArrayList" — specifically array-based here, since
spacesis declared as a plainHorse[]rather than anArrayList. This happens to land in the FRQ 3 slot that Array/ArrayList questions occupy on today's exams, though that fixed numbering pattern wasn't standardized until the 2019–2020 redesign, so it's coincidence rather than a rule that applied in 2012.
The Setup
- The given
Horseinterface (implemented elsewhere, not written here) declares:String getName()int getWeight()
- The
HorseBarnclass holds:private Horse[] spaces— each element either references aHorseoccupying that stall, or isnullfor an empty stall. No two horses in the barn share a name.
- You're asked to write two unrelated methods:
findHorseSpace(String name)— returns the index holding the horse with that name, or-1if none existsconsolidate()— moves every horse toward the front of the array, closing all gaps, without changing their relative order
Part (a): Writing findHorseSpace(String name)
The Rule, Broken Down
- Search every index of
spacesfor a horse whose name matches the one given. - Empty (
null) stalls never match anything — they have to be skipped, not compared. - Return the index of the first (and, since names are unique, only) match.
- If nothing matches after checking every stall, return
-1.
Step-by-Step Approach
- Loop over every index of
spaces. - At each index, first check whether the stall is occupied at all (
!= null) — only then is it safe to call a method on it. - If it's occupied, compare its horse's name to the target name with
.equals(). - On a match, return that index immediately.
- If the loop finishes with no match found, return
-1.
The Code
public int findHorseSpace(String name)
{
for (int i = 0; i < spaces.length; i++)
{
if (spaces[i] != null && spaces[i].getName().equals(name))
{
return i;
}
}
return -1;
}
Why Each Piece Matters
spaces[i] != null &&comes first, before.getName()is ever called — Java's&&short-circuits, so the second half never runs when the first half isfalse. Without this order (or without the null check at all), calling.getName()on an empty slot would throw aNullPointerException..equals(name), never==— two separately-builtStrings with identical characters aren't guaranteed to be the same object in memory, so==risks a false negative even when the names genuinely match.- Returning immediately inside the loop, rather than storing the result and returning after the loop, works cleanly here because the precondition guarantees at most one match exists — there's nothing left to find once one is located.
Tracing the Example
Using the question's own sweetHome barn — spaces holding "Trigger" (0), empty (1), "Silver" (2), "Lady" (3), empty (4), "Patches" (5), "Duke" (6):
| Call | What happens | Returned |
|---|---|---|
sweetHome.findHorseSpace("Trigger") |
matches immediately at index 0 | 0 |
sweetHome.findHorseSpace("Silver") |
skips index 0 (no match), skips index 1 (null), matches at index 2 |
2 |
sweetHome.findHorseSpace("Coco") |
checks every index, no match anywhere | -1 |
All three match the question's table exactly.
Common Mistakes to Avoid
- Skipping the
nullcheck entirely. Any barn with even one empty stall throws aNullPointerExceptionthe moment the loop reaches it. - Comparing names with
==instead of.equals(). This is one of the most common AP CSA point losses anywhereStringcomparison shows up. - Continuing to search after finding a match, or using a separate "found" flag instead of returning right away — unnecessary complexity given the "no two horses share a name" precondition.
Part (b): Writing consolidate()
The Rule, Broken Down
- Every horse currently in
spaceshas to end up packed into the front of the array — indices0,1,2, ... with no empty stall in between any two horses. - The horses' relative order must stay exactly the same as it was before consolidating.
- Whatever stalls are left over at the end become empty (
null).
Step-by-Step Approach
- Keep a second index,
dest, tracking the next open "front" position to fill — start it at0. - Scan through
spacesfrom the beginning with a regular loop indexi. - Whenever
spaces[i]holds a horse, that horse belongs atdestnext. Ifdestandiaren't already the same position, move it there and clear the old slot. - Either way, advance
destby one every time a horse is placed — that's the next open front slot for the next horse found. - Empty slots (
spaces[i] == null) are simply skipped;destdoesn't move for them.
The Code
public void consolidate()
{
int dest = 0;
for (int i = 0; i < spaces.length; i++)
{
if (spaces[i] != null)
{
if (dest != i)
{
spaces[dest] = spaces[i];
spaces[i] = null;
}
dest++;
}
}
}
Why Each Piece Matters
destonly ever moves forward, and only when a horse is placed — since horses are scanned left to right and always dropped in the same left-to-right order at the front, their relative order is automatically preserved without any extra bookkeeping.destcan never get ahead ofi.destonly increases when a horse is found, andialways increases every iteration regardless — sodest <= iat all times, meaningspaces[i]is always read before it could possibly be overwritten by an assignment tospaces[dest].- The
dest != icheck avoids a pointless self-assignment (and, more importantly, avoids nulling out a slot that hadn't actually moved) whenever a horse is already sitting exactly where it needs to be. spaces[i] = nullafter moving a horse — without this, the horse would appear to occupy two stalls at once until a later horse happens to overwrite the old slot.
Tracing the Example
Using the question's own barn — "Trigger" (0), empty (1), "Silver" (2), empty (3), empty (4), "Patches" (5), "Duke" (6):
i |
spaces[i] |
Action | dest after |
|---|---|---|---|
| 0 | "Trigger" |
dest == i (0 == 0), no move needed |
1 |
| 1 | empty | skipped | 1 |
| 2 | "Silver" |
move to index 1, clear index 2 | 2 |
| 3 | empty | skipped | 2 |
| 4 | empty | skipped | 2 |
| 5 | "Patches" |
move to index 2, clear index 5 | 3 |
| 6 | "Duke" |
move to index 3, clear index 6 | 4 |
Final array: "Trigger", "Silver", "Patches", "Duke", null, null, null — matching the question's expected result exactly, with all four horses still in their original relative order.
Common Mistakes to Avoid
- Forgetting to null out the old slot after moving a horse. Without it, a horse can appear to occupy its old stall and its new one simultaneously until something else overwrites the leftover reference.
- Reordering the horses accidentally, e.g. by scanning backward or using a nested loop that repeatedly finds "the next horse" without tracking where the last one landed — the straightforward single forward pass with a
destpointer is what guarantees order is preserved for free. - Advancing
desteven for empty slots.destshould only move when a horse is actually placed — advancing it for every index would leave gaps in the front of the array instead of closing them.
Key Takeaways
- A linear search through an array of possibly-
nullreferences always needs thenullcheck before calling a method on that element, combined with&&'s short-circuit behavior so the method call never happens on anullreference. - "Compact an array while preserving order" is a classic two-pointer pattern: a scanning index that visits every position, and a separate destination index that only advances when something is actually placed.
- Because the destination pointer in a compaction pass never moves faster than the scanning pointer, elements are always read before they could be overwritten — the whole operation is safe to do in place, without needing a second array.