CompSci.rocks
FRQcsapa

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 ArrayList while 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 MemberInfo class has:
    • A constructor: MemberInfo(String name, int gradYear, boolean hasGoodStanding)
    • int getGradYear() and boolean inGoodStanding() getters
  • The ClubMembers class 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 year
    • removeMembers(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

  1. Every name in the names array becomes a new club member.
  2. Every new member shares the same graduation year, gradYear.
  3. Every new member starts out in good standing.
  4. Order doesn't matter — the names can be added in any order.

Step-by-Step Approach

  1. Loop over every index of the names array.
  2. At each index, construct a new MemberInfo using that name, the shared gradYear, and true for good standing.
  3. Add that new MemberInfo object to memberList.

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, not memberList.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 a String directly to an ArrayList<MemberInfo>, it has to be wrapped.
  • true for the third constructor argument — the rule explicitly says every newly added member starts in good standing, so this is always true, never based on any condition.
  • memberList.add(...) — the no-index version of add appends to the end of the list, which is fine since order doesn't matter here.

Common Mistakes to Avoid

  • Looping over memberList instead of names. 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(...). memberList holds MemberInfo objects, not raw Strings.
  • Hardcoding false or 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

  1. A member has "graduated" if their graduation year is less than or equal to the year parameter.
  2. Every graduated member gets removed from memberList — regardless of whether they're in good standing.
  3. The method returns a separate list containing only the graduated members who were also in good standing.
  4. Members who haven't graduated yet are left alone in memberList, untouched.

Step-by-Step Approach

  1. Create an empty ArrayList<MemberInfo> to collect the graduated-and-in-good-standing members.
  2. Walk through memberList using a manually tracked index, rather than a simple for loop, because elements are going to be removed mid-loop.
  3. At each position, check whether that member has graduated (getGradYear() <= year).
  4. If they haven't graduated, move on to the next index normally.
  5. 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 memberList at the current index, and don't advance the index (the next member has just shifted into this position).
  6. 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 while loop with a manually managed index, instead of a for (int i = 0; i < memberList.size(); i++) loop — removing an element shrinks the list and shifts every later element one position to the left. A for loop's automatic i++ would then skip over the element that just slid into the removed spot.
  • i++ only happens in the else branch — when a member is removed, the very next member is now sitting at that same index i, so the loop needs to check index i again on the next pass, not move past it.
  • Checking inGoodStanding() before removing — both actions depend on the same member reference, so order between them doesn't actually matter here, but the good-standing check has to happen while you still have member in hand (which you always do, since it was already read into a variable).
  • memberList.size() re-evaluated every loop iteration — because it's in the while condition (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 <= 2018 is false, not graduated → i++i = 1
  • i = 1: Fox, grad year 2018 — 2018 <= 2018 is true, graduated. Good standing is true → add Fox to the result list. Remove index 1. List is now [Jane, Xin, Garcia]. i stays 1.
  • i = 1 again: the list shifted, so index 1 is now Xin, grad year 2017 — graduated. Good standing is false → don't add. Remove index 1. List is now [Jane, Garcia]. i stays 1.
  • i = 1 again: index 1 is now Garcia, grad year 2020 — 2020 <= 2018 is false, not graduated → i++i = 2
  • i = 2: memberList.size() is now 2, 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 for loop with i++ 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 i unconditionally, even after a removal. If a removal happened, incrementing i skips 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 from memberList needs 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 ArrayList while iterating over it requires a manually tracked index (or iterating backward) — never rely on a for loop's automatic increment when the list itself might shrink mid-loop.
  • When an index update depends on whether something happened this iteration, an if/else around the increment (rather than an unconditional i++) 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.

Related FRQs