CompSci.rocks
FRQcsapa

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 StepTracker class, 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 least minSteps steps count as "active" from then on
    • void addDailySteps(int steps) — records one more day's step count
    • int activeDays() — how many recorded days met the active threshold
    • double averageSteps() — total steps divided by number of days recorded, or 0.0 before 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

  1. 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.
  2. In the constructor, store the threshold and initialize every running value to 0.
  3. 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.
  4. In activeDays, just return the stored active count directly — nothing needs to be computed at call time.
  5. In averageSteps, guard against zero recorded days first (to avoid dividing by zero), otherwise divide the running total by the day count as a double.

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 = minSteps distinguishes the constructor parameter from the instance variable that shares its name — without this, the assignment would just assign the parameter to itself and leave the field untouched. (Naming the parameter something else, like min, would avoid needing this at all — either style is fine.)
  • numActiveDays is its own running counter, updated only inside addDailySteps, rather than recomputed later inside activeDays(). 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 == 0 check in averageSteps() exists specifically because the table shows tr.averageSteps() returning 0.0 before any data has been recorded. Without it, integer division by zero would throw an ArithmeticException, and even the floating-point version would produce NaN — neither matches the expected 0.0.
  • (double) totalSteps forces floating-point division the same way 2022's ReviewAnalysis.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 == 0 guard in averageSteps(). 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 >= against minSteps. The constructor's description says "at least minSteps," so a day with exactly the threshold amount must count as active.
  • Storing every individual day's steps in an ArrayList and 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 numActiveDays inside activeDays() by looping over stored data, instead of maintaining a running counter inside addDailySteps() — 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.

Related FRQs