FRQ
› csapa
ClubMembers: 2021 FRQ 3
A step-by-step solution to the 2021 AP CSA FRQ 3 (ClubMembers), covering building an ArrayList from an array and safely removing elements while iterating in Java.
Keeping track of a club roster is the scenario behind this AP Computer Science A free-response question — you add a batch of new members from a plain array, then remove graduated members from an ArrayList while simultaneously collecting the ones worth recognizing.
What This FRQ Tests
- AP CSA units: Unit 6 (Array) and Unit 7 (ArrayList)
- Core skill: looping through an array to build up an existing
ArrayList - Secondary skill: safely removing elements from an
ArrayListwhile looping over it, without skipping elements or corrupting the list - Official category: "Array/ArrayList" — always FRQ 3 on the AP CSA exam
The Setup
- The given
MemberInfoclass has:- A constructor:
MemberInfo(String name, int gradYear, boolean hasGoodStanding) int getGradYear()andboolean inGoodStanding()getters
- A constructor:
- The
ClubMembersclass holds:private ArrayList<MemberInfo> memberList
- You're asked to write two methods:
addMembers(String[] names, int gradYear)— adds a batch of new members, all sharing one graduation yearremoveMembers(int year)— removes every graduated member from the list, returning just the graduated ones who are in good standing
Part (a): Writing addMembers(String[] names, int gradYear)
The Rule, Broken Down
- Every name in the
namesarray becomes a new club member. - Every new member shares the same graduation year,
gradYear. - Every new member starts out in good standing.
- Order doesn't matter — the names can be added in any order.
Step-by-Step Approach
- Loop over every index of the
namesarray. - At each index, construct a new
MemberInfousing that name, the sharedgradYear, andtruefor good standing. - Add that new
MemberInfoobject tomemberList.
The Code
public void addMembers(String[] names, int gradYear)
{
for (int i = 0; i < names.length; i++)
{
memberList.add(new MemberInfo(names[i], gradYear, true));
}
}
Why Each Piece Matters
names.length, notmemberList.size()— the loop walks the array of incoming names, not the list being built up.new MemberInfo(names[i], gradYear, true)— a brand-new object is constructed for every name; you can't add aStringdirectly to anArrayList<MemberInfo>, it has to be wrapped.truefor the third constructor argument — the rule explicitly says every newly added member starts in good standing, so this is alwaystrue, never based on any condition.memberList.add(...)— the no-index version ofaddappends to the end of the list, which is fine since order doesn't matter here.
Common Mistakes to Avoid
- Looping over
memberListinstead ofnames. The array being read from and the list being written to are two different collections with two different lengths. - Forgetting to wrap each name in a
new MemberInfo(...).memberListholdsMemberInfoobjects, not rawStrings. - Hardcoding
falseor leaving good standing unset. Every member added by this method starts in good standing, by rule.
Part (b): Writing removeMembers(int year)
The Rule, Broken Down
- A member has "graduated" if their graduation year is less than or equal to the
yearparameter. - Every graduated member gets removed from
memberList— regardless of whether they're in good standing. - The method returns a separate list containing only the graduated members who were also in good standing.
- Members who haven't graduated yet are left alone in
memberList, untouched.
Step-by-Step Approach
- Create an empty
ArrayList<MemberInfo>to collect the graduated-and-in-good-standing members. - Walk through
memberListusing a manually tracked index, rather than a simpleforloop, because elements are going to be removed mid-loop. - At each position, check whether that member has graduated (
getGradYear() <= year). - If they haven't graduated, move on to the next index normally.
- If they have graduated: first check if they're in good standing and, if so, add them to the result list — then remove them from
memberListat the current index, and don't advance the index (the next member has just shifted into this position). - After the loop finishes, return the result list.
The Code
public ArrayList<MemberInfo> removeMembers(int year)
{
ArrayList<MemberInfo> graduatedGoodStanding = new ArrayList<MemberInfo>();
int i = 0;
while (i < memberList.size())
{
MemberInfo member = memberList.get(i);
if (member.getGradYear() <= year)
{
if (member.inGoodStanding())
{
graduatedGoodStanding.add(member);
}
memberList.remove(i);
}
else
{
i++;
}
}
return graduatedGoodStanding;
}
Why Each Piece Matters
- A
whileloop with a manually managed index, instead of afor (int i = 0; i < memberList.size(); i++)loop — removing an element shrinks the list and shifts every later element one position to the left. Aforloop's automatici++would then skip over the element that just slid into the removed spot. i++only happens in theelsebranch — when a member is removed, the very next member is now sitting at that same indexi, so the loop needs to check indexiagain on the next pass, not move past it.- Checking
inGoodStanding()before removing — both actions depend on the samememberreference, so order between them doesn't actually matter here, but the good-standing check has to happen while you still havememberin hand (which you always do, since it was already read into a variable). memberList.size()re-evaluated every loop iteration — because it's in thewhilecondition (not stored in a variable beforehand), it automatically reflects the list's new, smaller size after each removal.
Tracing the Example
Starting memberList (before removeMembers(2018)):
| Index | Name | Grad Year | Good Standing |
|---|---|---|---|
| 0 | "SMITH, JANE" |
2019 | false |
| 1 | "FOX, STEVE" |
2018 | true |
| 2 | "XIN, MICHAEL" |
2017 | false |
| 3 | "GARCIA, MARIA" |
2020 | true |
Walking through with i starting at 0:
i = 0: Jane, grad year 2019 —2019 <= 2018isfalse, not graduated →i++→i = 1i = 1: Fox, grad year 2018 —2018 <= 2018istrue, graduated. Good standing istrue→ add Fox to the result list. Remove index 1. List is now[Jane, Xin, Garcia].istays1.i = 1again: the list shifted, so index 1 is now Xin, grad year 2017 — graduated. Good standing isfalse→ don't add. Remove index 1. List is now[Jane, Garcia].istays1.i = 1again: index 1 is now Garcia, grad year 2020 —2020 <= 2018isfalse, not graduated →i++→i = 2i = 2:memberList.size()is now2, so the loop ends.
Final memberList: [Jane, Garcia] — matches the question's expected "after" list exactly. Returned list: [Fox] — matches the question's expected returned list exactly.
Common Mistakes to Avoid
- Using a
forloop withi++in the update clause while also removing elements inside the loop. This is the single most common bug on this problem — it silently skips the element that shifts into the just-removed position. - Incrementing
iunconditionally, even after a removal. If a removal happened, incrementingiskips the next element entirely. - Filtering by
inGoodStanding()alone and forgetting the graduation-year check, or vice versa — the returned list needs graduated and in good standing; removal frommemberListneeds graduated regardless of standing. Those are two different conditions applied to two different actions. - Removing non-graduated members, or leaving graduated-but-not-good-standing members in
memberList. Every graduated member is removed, whether or not they end up in the returned list.
Key Takeaways
- Removing elements from an
ArrayListwhile iterating over it requires a manually tracked index (or iterating backward) — never rely on aforloop's automatic increment when the list itself might shrink mid-loop. - When an index update depends on whether something happened this iteration, an
if/elsearound the increment (rather than an unconditionali++) is the cleanest way to express that. - "Filter into a new list while also modifying the original" problems almost always have two separate conditions in play — read carefully to see whether they're the same condition or different ones.