FRQ
› csapa
StepTracker: 2019 FRQ 2
A step-by-step solution to the 2019 AP CSA FRQ 2 (StepTracker), covering designing a complete class from scratch with running totals and counters in Java.
Fitness apps count steps every day and quietly keep a running tally behind the scenes — this AP Computer Science A free-response question asks you to build exactly that kind of tracker from nothing but a description and a table of expected behavior, with no starter class given at all.
What This FRQ Tests
- AP CSA units: Unit 5 (Writing Classes)
- Core skill: designing instance variables that accumulate state correctly across repeated method calls
- Secondary skill: recognizing when a division needs a special case to avoid dividing by zero
- Official category: "Classes" — always FRQ 2 on the AP CSA exam (this year's version is a standalone class rather than a subclass — see 2022's Book/Textbook for one built with inheritance)
The Setup
- No class skeleton is provided — the entire
StepTrackerclass, including its instance variables, is up to you to design. - Behavior is fully specified by the constructor parameter and three methods:
StepTracker(int minSteps)— days with at leastminStepssteps count as "active" from then onvoid addDailySteps(int steps)— records one more day's step countint activeDays()— how many recorded days met the active thresholddouble averageSteps()— total steps divided by number of days recorded, or0.0before any data exists
- The question includes a full worked execution sequence (reproduced below), and the finished class must return every value in it exactly.
Building the StepTracker Class
Step-by-Step Approach
- Decide what state needs to persist between calls: the active-day threshold (set once, in the constructor), a running total of steps, a count of how many days have been recorded, and a count of how many of those days were active.
- In the constructor, store the threshold and initialize every running value to
0. - In
addDailySteps, add the new steps to the running total and increment the day count — then, separately, check whether this day's steps meet the threshold and increment the active count if so. - In
activeDays, just return the stored active count directly — nothing needs to be computed at call time. - In
averageSteps, guard against zero recorded days first (to avoid dividing by zero), otherwise divide the running total by the day count as adouble.
The Code
public class StepTracker
{
private int minSteps;
private int totalSteps;
private int daysTracked;
private int numActiveDays;
public StepTracker(int minSteps)
{
this.minSteps = minSteps;
totalSteps = 0;
daysTracked = 0;
numActiveDays = 0;
}
public void addDailySteps(int steps)
{
totalSteps += steps;
daysTracked++;
if (steps >= minSteps)
{
numActiveDays++;
}
}
public int activeDays()
{
return numActiveDays;
}
public double averageSteps()
{
if (daysTracked == 0)
{
return 0.0;
}
return (double) totalSteps / daysTracked;
}
}
Why Each Piece Matters
this.minSteps = minStepsdistinguishes the constructor parameter from the instance variable that shares its name — withoutthis, the assignment would just assign the parameter to itself and leave the field untouched. (Naming the parameter something else, likemin, would avoid needingthisat all — either style is fine.)numActiveDaysis its own running counter, updated only insideaddDailySteps, rather than recomputed later insideactiveDays(). There's no stored list of individual daily step values to loop back over, so the count has to be kept current as data arrives.- The
daysTracked == 0check inaverageSteps()exists specifically because the table showstr.averageSteps()returning0.0before any data has been recorded. Without it, integer division by zero would throw anArithmeticException, and even the floating-point version would produceNaN— neither matches the expected0.0. (double) totalStepsforces floating-point division the same way 2022'sReviewAnalysis.getAverageRating()does — casting the numerator before the division happens, not the already-truncated result afterward.
Tracing the Example
Using the exact sequence from the question, with StepTracker tr = new StepTracker(10000):
| Call | totalSteps |
daysTracked |
numActiveDays |
Value returned |
|---|---|---|---|---|
tr.activeDays() |
0 | 0 | 0 | 0 |
tr.averageSteps() |
0 | 0 | 0 | 0.0 |
tr.addDailySteps(9000) |
9000 | 1 | 0 | — |
tr.addDailySteps(5000) |
14000 | 2 | 0 | — |
tr.activeDays() |
14000 | 2 | 0 | 0 |
tr.averageSteps() |
14000 | 2 | 0 | 7000.0 |
tr.addDailySteps(13000) |
27000 | 3 | 1 | — |
tr.activeDays() |
27000 | 3 | 1 | 1 |
tr.averageSteps() |
27000 | 3 | 1 | 9000.0 |
tr.addDailySteps(23000) |
50000 | 4 | 2 | — |
tr.addDailySteps(1111) |
51111 | 5 | 2 | — |
tr.activeDays() |
51111 | 5 | 2 | 2 |
tr.averageSteps() |
51111 | 5 | 2 | 10222.2 |
Every value returned matches the question's table exactly.
Common Mistakes to Avoid
- Forgetting the
daysTracked == 0guard inaverageSteps(). This is the detail most likely to be missed, since it only shows up in the very first call, before any data exists. - Comparing with
>instead of>=againstminSteps. The constructor's description says "at leastminSteps," so a day with exactly the threshold amount must count as active. - Storing every individual day's steps in an
ArrayListand recomputing totals and averages by looping through it every time a method is called. This works, but it's unnecessary bookkeeping when two running counters (totalSteps,daysTracked) already capture everything needed. - Recomputing
numActiveDaysinsideactiveDays()by looping over stored data, instead of maintaining a running counter insideaddDailySteps()— same wasted effort as the point above.
Key Takeaways
- When no class skeleton is given at all, the method signatures and the sample execution table together tell you exactly what state you need — work backward from what each method must return.
- A class that only needs running totals and counts doesn't need to store every individual data point in a list — plain instance variables, updated as data arrives, are simpler and just as correct.
- Any method that divides by "the number of things recorded so far" needs an explicit check for zero recorded things, or the very first call before any data exists will break it.