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
ArrayListwhile 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
ArrayListiteration-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
TimeIntervalclass (not modified) provides:boolean overlapsWith(TimeInterval interval)— returns whetherintervaloverlaps with this one
- The given
Appointmentclass provides:TimeInterval getTime()— this appointment's time intervalboolean conflictsWith(Appointment other)— to be written in part (a)
- The
DailyScheduleclass holds:private ArrayList apptList— a raw, non-genericArrayList(this is how the 2006 exam wrote it; reading an element back out with.get(i)returns a plainObject, so it needs an explicit(Appointment)cast), containing appointments that never overlap each othervoid 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
- Two appointments conflict exactly when their time intervals overlap.
TimeIntervalalready has a method,overlapsWith, that answers exactly that question for twoTimeIntervalobjects.conflictsWithdoesn't need any new logic of its own — it just needs to ask the right twoTimeIntervalobjects the right question.
Step-by-Step Approach
- Get this appointment's own time interval with
getTime(). - Get
other's time interval withother.getTime(). - Ask the first interval whether it overlaps with the second, using
overlapsWith. - 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 whoseoverlapsWithmethod actually gets called.other.getTime()is the argument passed intooverlapsWith, 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)TimeIntervalclass is implemented, that's not guaranteed.- No
if/elseneeded —overlapsWithalready returns aboolean, soconflictsWithcan hand that value straight back with a singlereturn.
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 ofgetTime().overlapsWith(other.getTime())— not necessarily wrong for a symmetric overlap check, but not what the method described is actually doing. - Comparing
TimeIntervalobjects with==instead of callingoverlapsWith.==would check whether they're the same object in memory, not whether their time ranges intersect. - Trying to reimplement overlap-checking logic from scratch.
TimeIntervalalready providesoverlapsWithfor 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
- Look at every appointment currently stored in
apptList. - If it conflicts with the given
appt(perconflictsWith, which you may assume works correctly), remove it fromapptList. - Every appointment that has a conflict must be removed — not just the first one found.
Step-by-Step Approach
- Loop over
apptListby index, but backward — starting atapptList.size() - 1and counting down to0. - At each index, pull out that
Appointment(casting the rawArrayList'sObjectresult) and checkconflictsWith(appt). - If it conflicts, remove it at that index.
- 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() - 1down to0—ArrayList.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 indexi, the element that used to be ati + 1slides into positioni, and the next iteration jumps toi + 1— skipping right over the element that just moved. Counting down avoids this entirely: removing indexionly shifts elements at indices greater thani, which have already been checked and won't be visited again. - The
(Appointment)cast is required becauseapptListis declared as a rawArrayList, notArrayList<Appointment>. Without the generic type,.get(i)returns a plainObject, which doesn't have aconflictsWithmethod until it's cast back toAppointment. - Checking every index, not stopping at the first match — the postcondition requires all conflicting appointments removed, and since
apptListis otherwise made of non-overlapping appointments, more than one existing appointment can independently overlap the same newappt.
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 rawArrayList's.get(i)result — this won't compile, sinceObjecthas noconflictsWithmethod. - Recomputing
apptList.size()inside the loop condition after already removing elements — this specific backward loop is safe becausesize()is only evaluated once, before the loop starts, to set the initiali.
Part (c): Writing addAppt(Appointment appt, boolean emergency)
The Rule, Broken Down
- If
emergencyistrue: clear out anything in the schedule that conflicts withappt, then addapptunconditionally. Always returnstrue. - If
emergencyisfalse: check whetherapptconflicts with anything already in the schedule. If nothing conflicts, add it and returntrue. If anything conflicts, don't add it, and returnfalse.
Step-by-Step Approach
- If
emergencyistrue, callclearConflicts(appt)to remove anything in the way, addappttoapptList, and returntrueright away. - Otherwise, loop forward through
apptList, checkingconflictsWith(appt)for each existing appointment. - If any conflict is found, return
falseimmediately — don't addappt. - If the loop finishes with no conflicts found, add
apptand returntrue.
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
clearConflictsinstead of re-writing a removal loop here — the problem explicitly says you may assumeclearConflicts"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
falsethe instant a conflict is found means the rest of the list is never checked, andapptnever reachesapptList.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
clearConflictseven whenemergencyisfalse. A non-emergency appointment should be rejected, not accepted by force-clearing the schedule around it. - Adding
apptbefore checking for conflicts in the non-emergency branch — this would let a conflicting appointment slip into the schedule. - Continuing to check the rest of
apptListafter already finding a conflict, or forgetting toreturn falseimmediately, 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
ArrayListwhile looping over it requires iterating backward (or otherwise accounting for the index shiftremovecauses) — 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 rawArrayList's.get(...)result always needs an explicit cast back to the stored type before calling that type's methods on it.