State and memory
Hook
You write code to count how many game pieces the robot has collected. You test it — the robot grabs ten pieces. Every log line says Collected: 1. It never gets past 1.
You check the code. It looks completely reasonable:
void periodic() {
int count = 0;
if (sensor.hasGamePiece()) {
count = count + 1;
System.out.println("Collected: " + count);
}
}
periodic() runs 50 times per second. Every call recreates count from scratch. By the time you print it, it’s been alive for roughly 0.02 milliseconds.
Core concept
Where you declare a variable determines how long it lives. A variable declared inside a method is destroyed when the method returns. A variable declared as a class field persists for the life of the object.
See the problem
Step through this buggy version. Watch what happens to count across two consecutive calls to periodic():
void periodic() { int count = 0; if (sensor.hasGamePiece()) { count = count + 1; System.out.println(count); }}Step through to see values update.
The method returns, count is destroyed, and on the next call we start from zero again. The robot counts to 1 fifty times per second.
The fix
Move count outside periodic() — make it a field on the class. It gets created once at startup and survives every call:
int count = 0; // field — lives on the objectvoid periodic() { if (sensor.hasGamePiece()) { count = count + 1; System.out.println(count); }}Step through to see values update.
The only change: int count = 0 moved from inside periodic() to the class body. Same syntax, completely different lifetime.
Where to put things
Every variable you write, ask: how long does this value need to live?
| Situation | Declare as |
|---|---|
| Only needed inside this one call | Local variable (inside the method) |
Needs to survive between calls to periodic() | Class field |
| Needs to be shared across multiple subsystems | Passed as a parameter, or stored in a shared class |
In WPILib, your Subsystem class is created once at startup by RobotContainer. Fields on a Subsystem live for the entire match. Almost all robot state — positions, counts, modes, setpoints — belongs there.
static fields are shared across every instance of a class. For FRC subsystems, you almost never want this — it means two instances would share one counter. Use instance fields unless you have a specific reason not to.
Try it yourself
A subsystem has a field speed = 0.5 and a local variable boost. Trace three consecutive calls to periodic(), each with turboHeld = true. What is speed after the third call?
double speed = 0.5; // field
void periodic() {
double boost = 0.0;
if (turboHeld) boost = boost + 0.2;
speed = speed + boost;
}double speed = 0.5; // fieldvoid periodic() { double boost = 0.0; if (turboHeld) boost = boost + 0.2; speed = speed + boost;}Step through to see values update.
After 3 calls with turboHeld = true, what is speed?
Key takeaways
- Local variables are created when a method is called and destroyed when it returns. They don’t remember anything.
- Class fields persist for the life of the object — across every call to
periodic(). - Most robot state (positions, counts, modes, setpoints) should be fields.
- Before declaring a variable, ask: does this value need to outlive this function call?
Common confusions
“Why not just make everything a field?” Local variables communicate intent — “this value only matters right now.” Overusing fields makes it harder to understand what the robot is remembering and when. Use local variables for intermediate calculations you don’t need to keep.
“My field resets when I redeploy.” Redeploying reboots the JVM — all fields reset to their initial values. If you need something to survive a reboot, you’d have to write it to disk or read it from a sensor. In FRC this is rare; most state should reset on startup anyway.
Challenge
A counter field starts at 0. Simulate four calls to a periodic() method that increments it by 1 each call and prints the new value. Your output should be four lines: 1, 2, 3, 4.
Stuck? Show hint
Just call periodic() four times in main. The field count keeps its value between calls.
What’s next
In Lesson 03, we’ll look at conditionals — the if/else structures that let a program choose different behavior based on sensor values.