CompSci.rocks
FRQcsapa

Appointment: 2006 FRQ 1

A step-by-step solution to the 2006 AP CSA FRQ 1 (Appointment/DailySchedule), covering delegating to an existing method, safely removing matches from an ArrayList while iterating, and combining both into a single booking method in Java.

An appointment-scheduling system built from three cooperating classes is the setting for this AP Computer Science A free-response question — you write one small method that leans entirely on a method you're given, then build two more that manage a list of appointments and keep it free of scheduling conflicts.

What This FRQ Tests

  • AP CSA units: Unit 5 (Writing Classes/methods), Unit 3 (Boolean Expressions and if Statements), and Unit 7 (ArrayList)
  • Core skill: writing a short method that simply delegates to an already-provided method, and combining boolean results from smaller pieces into a larger conditional
  • Secondary skill: safely removing matching elements from an ArrayList while looping over it, without skipping any elements
  • Official category: closest to "Methods and Control Structures" — officially printed as FRQ 1 on the 2006 exam. This particular question also leans on ArrayList iteration-with-removal for two of its three parts, so it doesn't sort as cleanly into one bucket as some later years do. (The fixed one-category-per-slot pattern used on modern exams — Methods and Control Structures, Classes, Array/ArrayList, 2D Array, always in that order — wasn't standardized until the 2019–2020 Course and Exam Description redesign.)

The Setup

  • The given TimeInterval class (not modified) provides:
    • boolean overlapsWith(TimeInterval interval) — returns whether interval overlaps with this one
  • The given Appointment class provides:
    • TimeInterval getTime() — this appointment's time interval
    • boolean conflictsWith(Appointment other) — to be written in part (a)
  • The DailySchedule class holds:
    • private ArrayList apptList — a raw, non-generic ArrayList (this is how the 2006 exam wrote it; reading an element back out with .get(i) returns a plain Object, so it needs an explicit (Appointment) cast), containing appointments that never overlap each other
    • void clearConflicts(Appointment appt) — to be written in part (b)
    • boolean addAppt(Appointment appt, boolean emergency) — to be written in part (c)

Part (a): Writing conflictsWith(Appointment other)

The Rule, Broken Down

  1. Two appointments conflict exactly when their time intervals overlap.
  2. TimeInterval already has a method, overlapsWith, that answers exactly that question for two TimeInterval objects.
  3. conflictsWith doesn't need any new logic of its own — it just needs to ask the right two TimeInterval objects the right question.

Step-by-Step Approach

  1. Get this appointment's own time interval with getTime().
  2. Get other's time interval with other.getTime().
  3. Ask the first interval whether it overlaps with the second, using overlapsWith.
  4. Return that result directly — there's nothing left to compute.

The Code

public boolean conflictsWith(Appointment other)
{
    return getTime().overlapsWith(other.getTime());
}

Why Each Piece Matters

  • getTime() with no object in front of it refers to this appointment's interval — the one whose overlapsWith method actually gets called.
  • other.getTime() is the argument passed into overlapsWith, not the object it's called on. Mixing these up (other.getTime().overlapsWith(getTime())) would still likely give the same answer for a symmetric overlap check, but it doesn't match what the method is actually asking, and depending on how the (unseen) TimeInterval class is implemented, that's not guaranteed.
  • No if/else neededoverlapsWith already returns a boolean, so conflictsWith can hand that value straight back with a single return.

Tracing the Example

The official 2006 prompt doesn't give any concrete appointment times or a worked example for this part — TimeInterval and overlapsWith are both left as "implementation not shown," so there's no numeric table to check conflictsWith against directly. Since the whole method is a one-line delegation, its correctness rests entirely on overlapsWith already working correctly, which the problem tells you to assume. A combined, illustrative trace of all three methods together (using simple made-up appointment times, not from the official prompt) appears under part (c) below.

Common Mistakes to Avoid

  • Reversing the call to other.getTime().overlapsWith(getTime()) instead of getTime().overlapsWith(other.getTime()) — not necessarily wrong for a symmetric overlap check, but not what the method described is actually doing.
  • Comparing TimeInterval objects with == instead of calling overlapsWith. == would check whether they're the same object in memory, not whether their time ranges intersect.
  • Trying to reimplement overlap-checking logic from scratch. TimeInterval already provides overlapsWith for exactly this purpose — writing your own interval-comparison logic here duplicates work and risks getting the edge cases (touching endpoints, etc.) wrong.

Part (b): Writing clearConflicts(Appointment appt)

The Rule, Broken Down

  1. Look at every appointment currently stored in apptList.
  2. If it conflicts with the given appt (per conflictsWith, which you may assume works correctly), remove it from apptList.
  3. Every appointment that has a conflict must be removed — not just the first one found.

Step-by-Step Approach

  1. Loop over apptList by index, but backward — starting at apptList.size() - 1 and counting down to 0.
  2. At each index, pull out that Appointment (casting the raw ArrayList's Object result) and check conflictsWith(appt).
  3. If it conflicts, remove it at that index.
  4. Keep going until every index has been checked.

Looping backward is the key idea here, not an arbitrary style choice — see below.

The Code

public void clearConflicts(Appointment appt)
{
    for (int i = apptList.size() - 1; i >= 0; i--)
    {
        Appointment current = (Appointment) apptList.get(i);

        if (current.conflictsWith(appt))
        {
            apptList.remove(i);
        }
    }
}

Why Each Piece Matters

  • Looping backward, from size() - 1 down to 0ArrayList.remove(int index) shifts every element after the removed index one position to the left. If the loop went forward instead and removed the element at index i, the element that used to be at i + 1 slides into position i, and the next iteration jumps to i + 1 — skipping right over the element that just moved. Counting down avoids this entirely: removing index i only shifts elements at indices greater than i, which have already been checked and won't be visited again.
  • The (Appointment) cast is required because apptList is declared as a raw ArrayList, not ArrayList<Appointment>. Without the generic type, .get(i) returns a plain Object, which doesn't have a conflictsWith method until it's cast back to Appointment.
  • Checking every index, not stopping at the first match — the postcondition requires all conflicting appointments removed, and since apptList is otherwise made of non-overlapping appointments, more than one existing appointment can independently overlap the same new appt.

Tracing the Example

Again, the official prompt gives no numeric example for this part. Here's a small illustrative trace (not from the official prompt) using simple hour-based time intervals, where two intervals overlap if their hour ranges intersect:

Suppose apptList starts as [A(9–10), C(11–12), D(14–15)] (already non-overlapping, as required), and clearConflicts is called with a new appointment X(9:30–11:30):

i Appointment Overlaps X (9:30–11:30)? Action
2 D (14–15) no keep
1 C (11–12) yes (11–11:30 overlaps) remove index 1
0 A (9–10) yes (9:30–10 overlaps) remove index 0

Final apptList: [D(14–15)] — both conflicting appointments are gone, and the one that didn't conflict is untouched.

Common Mistakes to Avoid

  • Looping forward while removing (for (int i = 0; i < apptList.size(); i++)). This is the single most common bug with this pattern — it silently skips checking some elements after any removal happens.
  • Forgetting the (Appointment) cast on a raw ArrayList's .get(i) result — this won't compile, since Object has no conflictsWith method.
  • Recomputing apptList.size() inside the loop condition after already removing elements — this specific backward loop is safe because size() is only evaluated once, before the loop starts, to set the initial i.

Part (c): Writing addAppt(Appointment appt, boolean emergency)

The Rule, Broken Down

  1. If emergency is true: clear out anything in the schedule that conflicts with appt, then add appt unconditionally. Always returns true.
  2. If emergency is false: check whether appt conflicts with anything already in the schedule. If nothing conflicts, add it and return true. If anything conflicts, don't add it, and return false.

Step-by-Step Approach

  1. If emergency is true, call clearConflicts(appt) to remove anything in the way, add appt to apptList, and return true right away.
  2. Otherwise, loop forward through apptList, checking conflictsWith(appt) for each existing appointment.
  3. If any conflict is found, return false immediately — don't add appt.
  4. If the loop finishes with no conflicts found, add appt and return true.

The Code

public boolean addAppt(Appointment appt, boolean emergency)
{
    if (emergency)
    {
        clearConflicts(appt);
        apptList.add(appt);
        return true;
    }

    for (int i = 0; i < apptList.size(); i++)
    {
        Appointment current = (Appointment) apptList.get(i);

        if (current.conflictsWith(appt))
        {
            return false;
        }
    }

    apptList.add(appt);
    return true;
}

Why Each Piece Matters

  • Reusing clearConflicts instead of re-writing a removal loop here — the problem explicitly says you may assume clearConflicts "works as specified, regardless of what you wrote in part (b)," which is a signal to build on it rather than duplicate it.
  • The conflict-search loop runs forward, unlike part (b)'s backward loop — nothing is being removed here, so there's no shifting-index problem to guard against. Backward iteration is only necessary when deleting elements mid-loop.
  • Returning false the instant a conflict is found means the rest of the list is never checked, and appt never reaches apptList.add(...) afterward — both are important, since adding it anyway after detecting a conflict would leave the schedule with an unresolved overlap.

Tracing the Example

The official prompt doesn't provide numeric appointment data for this question at all — unlike FRQ 2 and FRQ 3 from the same exam, there's no table or sample call to check against here. The trace below uses the same style of made-up hour-based intervals as part (b), purely to confirm the logic (not values taken from the official prompt).

Starting with apptList = [A(9–10), C(11–12)]:

Call Conflict check Result
addAppt(Y(10–10:30), false) Y doesn't overlap A (touches at 10, doesn't overlap) or C no conflict → Y added, returns true. apptList = [A, C, Y]
addAppt(Z(11:30–12:30), false) Z overlaps C(11–12) from 11:30–12 conflict found → Z not added, returns false. apptList unchanged
addAppt(Z(11:30–12:30), true) emergency is true, so clearConflicts(Z) runs first, removing C Z added unconditionally, returns true. apptList = [A, Y, Z]

The last call shows why the emergency branch has to come first and skip the conflict-checking loop entirely: the same Z that was rejected as a normal appointment is accepted as an emergency, precisely because emergencies are allowed to displace whatever was in the way.

Common Mistakes to Avoid

  • Calling clearConflicts even when emergency is false. A non-emergency appointment should be rejected, not accepted by force-clearing the schedule around it.
  • Adding appt before checking for conflicts in the non-emergency branch — this would let a conflicting appointment slip into the schedule.
  • Continuing to check the rest of apptList after already finding a conflict, or forgetting to return false immediately, which could let execution fall through to the "add it" code at the bottom.

Key Takeaways

  • A method that only needs to combine or forward the results of other already-provided methods doesn't need new logic of its own — the "rule" and the "method body" can be nearly identical.
  • Removing matching elements from an ArrayList while looping over it requires iterating backward (or otherwise accounting for the index shift remove causes) — looping forward silently skips elements.
  • Once a helper method (like clearConflicts) is written and trusted, later methods should call it directly instead of re-deriving the same logic — and a raw ArrayList's .get(...) result always needs an explicit cast back to the stored type before calling that type's methods on it.

Related FRQs