AP CSA 3.1: Abstraction and Program Design
AP CSA 3.1: Abstraction and Program Design
Before you type a class, decide what an object knows and what it does. Nouns become attributes, verbs become behaviors, and one question decides whether an attribute belongs to the object or to the whole class.
Abstraction means keeping what matters and discarding what does not. To design a class, read the specification, turn its nouns into attributes and its verbs into behaviors, then ask one question about each attribute: does every object need its own value? If yes it is an instance variable; if all objects share one value it is a class variable, marked static. Attributes are private unless the specification says otherwise. That is the whole of AP CSA 3.1, and it is the first two points of every Class Design free-response question.
What you will learn
- Define abstraction, data abstraction, and procedural abstraction, and say what each one hides
- Turn a written specification into a list of attributes and a list of behaviors
- Choose between an instance variable and a class variable using the one design question
- Explain why attributes are private, and what breaks when they are not
- Reject an attribute that should be a computed behavior, and one that belongs to another class
- Predict what a design produces before it is written, including shared-counter and aliasing cases
Why this matters on the exam
Under the 2025 CED, AP CSA is four units, and Unit 3: Class Creation is worth roughly a quarter of the multiple-choice section. Its real weight is larger than that number suggests, because free-response question 2 is Class Design every single year. You are handed a scenario and a table of calls and results, and you write the class from nothing: the header, the private instance variables, the constructor, and the methods.
Lesson 3.1 is where the points on that question are decided. Two of the four rubric points in a typical Class Design question go to the attributes and the constructor, and the single most common way to lose them is a design error rather than a syntax error: an attribute declared per object when the specification describes one shared value, a value stored that should have been computed, or a public field where the specification implies the class protects its own data. None of those produce a compiler message. They produce working code that is wrong, which is exactly what this lesson trains you to catch.
On the multiple-choice side, 3.1 shows up as design-judgment questions: which class design is best, which of I, II and III are appropriate instance variables, and what a program prints when a static counter is involved. Those questions are answerable in fifteen seconds if you have the one design question memorized, and are near-coin-flips if you do not.
The problem this solves
A campus wants an app for its scooter fleet. Somebody has to decide, before any code exists, what a scooter is to the program. Its color and its serial number plate do not matter. Its battery level, whether it is currently rented, and the fleet's running count of scooters do. That decision, made on paper in about five minutes, is the difference between a class you can finish and one you rewrite twice. FRQ 2 on the exam hands you a scenario and a table of interactions and asks you to make exactly that decision, then implement it.
Design first: turn a real thing into a class
Abstraction is reducing complexity by focusing on the main idea and hiding the details that do not matter for the question at hand. Designing a class is abstraction in two directions at once: you decide what an object knows and what an object does, and you throw away everything else.
The habit that pays off on FRQ 2 is simple. Read the specification, underline the nouns, underline the verbs. The nouns become attributes (the data). The verbs become behaviors (the methods). Do that on paper before you type a single line.
Nouns become attributes, verbs become behaviors
// Spec: a scooter has a battery level and // can be rented, parked, and ridden. public class Scooter { // attributes: what it KNOWS private int battery; private boolean rented; // behaviors: what it DOES public void rent() { ... } public void park() { ... } public void ride(int minutes) { ... } }
| noun | attribute (data) |
| verb | behavior (method) |
| adjective | usually a boolean |
| "how many" | usually an int |
Scooter for its battery level; they never need to know it is an int field named battery.One per object, or one for the whole class?
An attribute is declared in the class, outside every method and constructor. There are two kinds, and choosing the wrong one is the single most common design mistake in this unit.
public class Ticket { // class variable: ONE copy, shared private static int issued = 0; // instance variable: one PER object private int number; public Ticket() { issued++; number = issued; } }
| instance variable | one copy per object |
| class variable | one copy, shared by all |
| keyword |
static marks a class variable |
| test | "does each object need its own?" |
Ask one question: does every object need its own value? Each ticket needs its own number, so that is an instance variable. The running count of how many tickets exist belongs to the whole class, so that is a class variable.
static from issued, every Ticket gets its own private counter that starts at 0. Each new ticket then reports number 1, and nothing about the code looks broken. The compiler will not warn you. Only tracing the design catches it.Procedural abstraction: name it, decompose it, generalize it
A procedural abstraction gives a process a name so it can be used knowing only what it does, not how. Three moves follow from it.
-
Name it.
isAvailable()says what it means;battery >= 20 && !rentedmakes the reader re-derive it every time. - Decompose it. Break a large behavior into smaller methods, each doing one job. Method decomposition is what turns a 40-line method into four readable ones.
- Generalize it with a parameter. Three near-identical methods almost always want to be one method with a parameter.
// duplicated: three near-copies public int priceWithTax5() { ... } public int priceWithTax7() { ... } public int priceWithTax9() { ... } // generalized: one method, any rate public int priceWithTax(int percent) { return subtotal + subtotal * percent / 100; }
| less code | one place to fix a bug |
| reusable | works for a rate you never listed |
| parameters | are what make it general |
Vocabulary
| Term | Meaning |
|---|---|
| Abstraction | Reducing complexity by focusing on the main idea and hiding details that do not matter for the question at hand. |
| Data abstraction | Giving data a name without referencing how it is actually stored, so callers depend on the name and not the representation. |
| Attribute | A data abstraction declared in a class outside every method and constructor. Attributes are either instance variables or class variables. |
| Instance variable | An attribute whose value is unique to each object. Every object carries its own copy. |
| Class variable | An attribute shared by all objects of the class. One copy exists, marked with the static keyword. |
| Behavior | Something an object can do, implemented as a method. Behaviors come from the verbs in the specification. |
| Procedural abstraction | Naming a process so it can be used knowing only what it does, not how it does it. |
| Method decomposition | Breaking a large behavior into smaller methods, each responsible for one job. |
| Precondition | What must already be true when a method is called for it to work as described. It is documented, not enforced. |
Predict first, then check
ride runs battery -= minutes / 3;. A scooter starts at battery 40, then ride(7) and ride(5) are called. What is battery?private static int made = 0; and its constructor runs made++. After three objects are created, what does made hold?Common misconceptions
Each of these is a real answer students give. Read the wrong version first and decide why it is wrong before you read the fix.
Confusing an attribute with a behavior
addSong belongs in the attribute list."
Making everything an instance variable
count field."
static and there is exactly one copy. Give each object its own copy instead and every object counts to 1 and stops. The program compiles, runs, and is silently wrong, which is why this is the defect the exam reaches for most often.
Making everything static to avoid the decision
static made the error go away, so I will put it on every field."
static attribute is shared, so marking a per-object value static makes every object report the same thing. Two bank accounts would share one balance. The keyword is a design statement, not a repair tool: use it only when the specification says all objects share one value.
Storing a value that should be computed
heating flag so it does not have to work it out every time."
Leaving instance variables public
private unless the specification says otherwise: on the exam a public instance variable is almost always a distractor.
See the design in memory
Two Scooter objects and one shared class variable. Watch each object get its own copy of the instance variables while the class variable count is shared, then watch a second name point at an existing object.
Live code editor
Eight problems, six scaffolded and two open-ended. Write Java and press Run to execute it. When you are ready, press Submit to test it against the expected output; the reference solution then appears underneath, next to your own code rather than on top of it. Hints are there if you are stuck, and attempts and hints are both tracked.
A Kiln fires pottery in a studio that has several of them. The class is finished except for its three attributes.
The three attributes
- temperature
- the current temperature of this kiln
- target
- the temperature this kiln is set to
- firing
- how many kilns in the whole studio are firing
The two forms you can choose between
- private int example;
- one copy per object
- private static int shared = 0;
- one copy for the whole class
A Ledger tracks money for one club. It stores what came in and what went out, and nothing else.
Attributes, already declared for you
- deposits
- total cents ever put into this ledger
- spends
- total cents ever taken out of this ledger
The two behaviors to fill in
- int getBalance()
- works the balance out from deposits and spends
- boolean canSpend(int cents)
- true when the balance covers that amount exactly or more
Design Decisions Arcade
Six quick rounds. Read the class and the client code, then type the final value. These are the six design traps that show up again and again on FRQ 2: per-object versus shared, integer division, reference copying, and parameter copying.
public class S {
private int b;
public S(int start) { b = start; }
public void ride(int m) { b -= m / 3; }
public int get() { return b; }
}
S s = new S(60);
s.ride(11);
s.ride(7);
int x = s.get();
public class T {
private static int n = 0;
public T() { n++; }
public static int get() { return n; }
}
T a = new T();
T b = new T();
T c = new T();
T d = new T();
int x = T.get();
public class T {
private int n = 0;
public T() { n++; }
public int get() { return n; }
}
T a = new T();
T b = new T();
int x = b.get();
public class Box {
private int v;
public Box(int start) { v = start; }
public void set(int n) { v = n; }
public int get() { return v; }
}
Box p = new Box(4);
Box q = p;
q.set(12);
int x = p.get();
public class M {
public static void bump(int n) { n += 100; }
}
int x = 5;
M.bump(x);
public class Meter {
private int min;
private int rate;
public Meter(int r) { rate = r; min = 0; }
public void insert(int c) { min += c / rate; }
public int get() { return min; }
}
Meter m = new Meter(20);
m.insert(95);
m.insert(50);
int x = m.get();
Multiple choice
Predict your answer before reading the options. Watch for the qualifier words. After you answer, the rationale explains every distractor.
public class Tank {
private int gas;
public Tank(int start) {
gas = start;
}
public void drive(int miles) {
gas -= miles / 4;
}
public int getGas() {
return gas;
}
}
Tank t = new Tank(30);
t.drive(9);
t.drive(7);
System.out.println(t.getGas());
public class Ticket {
private int issued = 0;
private int number;
public Ticket() {
issued++;
number = issued;
}
public int getNumber() {
return number;
}
}
public class Locker {
private static int built = 0;
private int capacity;
private int count;
public Locker(int cap) {
built++;
capacity = cap;
count = 0;
}
public static int getBuilt() { return built; }
public int getCount() { return count; }
}
Locker a = new Locker(3);
Locker b = new Locker(5);
I. a and b hold different values for capacity
II. Locker.getBuilt() returns 2
III. a and b share one single count variable
public class Playlist {
// attributes go here
// behaviors go below
}
public class Cart {
private int subtotal;
public int priceWithTax5() { ... }
public int priceWithTax7() { ... }
public int priceWithTax9() { ... }
}
public class Inventory {
public int count;
public Inventory(int start) {
if (start < 0) {
start = 0;
}
count = start;
}
public void remove(int n) {
if (n <= count) {
count -= n;
}
}
}
public class LoanCopy {
// one of these four is declared at the wrong level
}
public class Kiln {
// attributes?
// behaviors?
}
I. servings, the number of portions this recipe makes
II. totalCalories, found by adding up every ingredient
III. printedCopies, how many times any recipe in the
app has ever been printed
public class Wristband {
private static int made = 0;
private int id;
public Wristband() {
id = made;
made++;
}
public int getId() {
return id;
}
}
Wristband a = new Wristband();
Wristband b = new Wristband();
Wristband c = a;
Wristband d = new Wristband();
System.out.println(d.getId());
public class Meter {
private int minutes;
private int rate;
public Meter(int centsPerMinute) {
rate = centsPerMinute;
minutes = 0;
}
public void insert(int cents) {
minutes += cents / rate;
}
public void tick(int n) {
minutes -= n;
if (minutes < 0) {
minutes = 0;
}
}
public int getMinutes() {
return minutes;
}
}
Meter m = new Meter(25);
Meter same = m;
m.insert(70);
same.insert(60);
same.tick(4);
System.out.println(m.getMinutes());
FRQ Practice
Free-response question 2 on the AP Exam is Class Design, and it is the one question that is Unit 3 from top to bottom. It hands you a scenario and a table showing how the class is used and what each call produces, and you build the class to match.
That question has two halves. The first is the design: which attributes exist, which level each one belongs at, and which values are computed rather than stored. The second is the syntax that expresses it, which arrives across 3.3 to 3.7. This is the design half, worth 4 points. Every header below is written for you; the decisions are not.
A gym issues a membership card to each member. A card counts that member's check-ins and knows the monthly fee that member pays. The gym also wants to know how many cards it has issued in total.
| Call | Result |
|---|---|
| new MemberCard(4000) | a card at 4000 cents a month, 0 visits so far |
| costPerVisit() | 4000 before any visit, because dividing by zero visits is not allowed |
| checkIn() three times, then costPerVisit() | 4000 / 3 = 1333, whole cents only |
| isFrequent() | false at 3 visits, true at exactly 10 |
| MemberCard.getIssued() | every card the gym has issued, not per member |
Scoring: 1 point for placing the three attributes at the right level, 1 point for the constructor doing all three of its jobs, 1 point for isFrequent landing exactly on the boundary, and 1 point for costPerVisit using integer division and guarding the no-visits case.
A gym issues a MemberCard to each member. A card counts that member's check-ins and knows the monthly fee they pay. The gym also wants to know how many cards it has issued in total.
Attributes to declare
- visits
- check-ins by this member
- monthlyFee
- cents this member pays each month
- issued
- cards the whole gym has issued
Behaviors, headers already written
- MemberCard(int fee)
- records the fee, starts visits at 0, and adds one to the gym's issued total
- void checkIn()
- records one visit for this member
- boolean isFrequent()
- true once this member reaches 10 visits
- int costPerVisit()
- the monthly fee divided by the visits so far, in whole cents
Choose your lane
Support
- Reread the Code and Memory panels
- Editor problems 1 to 3 with hints on
- Retry the quiz until 80%
- Use the FRQ method shell
Core
- All 8 editor problems
- Full game run
- The 12-question quiz once
- The FRQ connection
Challenge
- Open-ended problems 7 and 8 with no hints
- Rewrite one loop a different way
- Extend the FRQ
- Lead a class trace
Summary
Designing a class is abstraction applied twice. You decide what an object knows, which becomes its attributes, and what an object does, which becomes its behaviors, and you discard everything the program has no use for. The mechanical version of that is to read the specification, underline the nouns and the verbs, and let the two lists become the two halves of the class.
Every attribute then faces one question: does every object need its own value? A per-object answer means an instance variable, one copy for every object. A shared answer means a class variable, declared static, with exactly one copy no matter how many objects exist. Getting this backwards is the defining error of the unit, and it never announces itself: a per-object counter compiles cleanly and reports 1 forever.
Two more rules finish the design. Store only what is independent, and compute anything that can be derived, so the object cannot hold two facts that disagree. And keep attributes private, because a class can only guarantee a rule when all access to its data runs through its own methods. Procedural abstraction is the same instinct applied to code: name a process, decompose a long behavior into small ones, and replace near-identical methods with one method that takes a parameter.
Key takeaways
- Abstraction is deciding what to ignore. Design is choosing which details the class does not carry.
- Nouns become attributes, verbs become behaviors. Do this on paper before typing.
- One design question settles instance vs class: does every object need its own value?
-
staticmeans one shared copy for the whole class, not "a fix for an error". - Store independent data; compute anything that follows from it, so nothing can go stale.
- Attributes are
privateunless the specification says otherwise. Public fields break the guarantee. - A design bug compiles and runs. Tracing the design, not the compiler, is what catches it.
Frequently asked questions
What is the difference between an instance variable and a class variable?
An instance variable gets one copy per object, so two objects can hold different values for it. A class variable is declared with static and there is exactly one copy shared by every object of the class, so changing it through one object changes it for all of them. The test is a single question: does every object need its own value? Each bank account needs its own balance, so balance is an instance variable. The number of accounts the bank has opened is one fact about the whole class, so it is static.
Why should instance variables be private?
Because a class can only enforce a rule when every path to its data goes through its own code. If a field is public, a caller can assign whatever it likes and the validation inside the methods never runs. Making it private and exposing behaviors instead is encapsulation, and it is also what lets you change how a value is stored later without breaking any calling code. On the AP exam, treat a public instance variable as a red flag unless the specification explicitly asks for one.
How much of the AP CSA exam is Unit 3?
Under the 2025 CED four-unit structure, Unit 3: Class Creation is about a quarter of the multiple-choice section, and it is the entirety of free-response question 2, which is a Class Design task every year. That question hands you a scenario and a table of calls, and you write the class from scratch. Lesson 3.1 is where its first two rubric points, the attributes and the constructor, are won or lost.
How do I decide what belongs in a class and what does not?
Ask what the program needs to do. An attribute earns its place if it is data this object owns, it cannot be computed from the other attributes, and it varies from object to object. That rules out three common mistakes at once: details that belong to a different class, values that should be derived in a method, and app-wide totals that should be static. Anything left over is the abstraction you keep.
What is the difference between data abstraction and procedural abstraction?
Data abstraction hides how a value is stored. Callers use getSeconds(), so the class is free to store tenths of a second internally without any calling code changing. Procedural abstraction hides how a process works. Callers use isAvailable() and never see the condition behind it, so that condition can be rewritten freely. Both trade a hidden detail for a stable name, which is what makes a program possible to change later.
Do I need to know UML diagrams for the AP CSA exam?
No. The AP CSA exam does not require you to read or draw UML. Class diagrams are a useful way to sketch attributes and behaviors while you plan, and some textbooks lean on them, but the exam gives you a written specification and a table of calls with their results. Practice reading that format instead, because that is the one free-response question 2 will hand you.
Related lessons
Unit 3: Class Creation
All 9 lessons in this unit. You are on 3.1.
- 3.1 Abstraction and Program Design
- 3.2 Impact of Program Design
- 3.3 Anatomy of a Class
- 3.4 Constructors
- 3.5 Methods: How to Write Them
- 3.6 Methods: Passing and Returning Object References
- 3.7 Class Variables and Methods
- 3.8 Scope and Access
- 3.9 The this Keyword
Test yourself on this unit
The rest of the course
Get in Touch
Whether you're a student, parent, or teacher — I'd love to hear from you.
Just want free AP CS resources?
Enter your email below and check the subscribe box — no message needed. Students get daily practice questions and study tips. Teachers get curriculum resources and teaching strategies.
Message Sent!
Thanks for reaching out. I'll get back to you within 24 hours.
Prefer email? Reach me directly at [email protected]