498
Control Lab FRC Programming Curriculum
Fundamentals · L02 of 8

State and memory

Prereqs: fundamentals-01
Objectives 0 / 3

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

Key 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():

Code Tracer
1void periodic() {
2 int count = 0;
3 if (sensor.hasGamePiece()) {
4 count = count + 1;
5 System.out.println(count);
6 }
7}
State
call1

Step through to see values update.

ConsoleNo output yet
Press Start to begin stepping through the code.
Initial state

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:

Code Tracer
1int count = 0; // field — lives on the object
2
3void periodic() {
4 if (sensor.hasGamePiece()) {
5 count = count + 1;
6 System.out.println(count);
7 }
8}
State
call1
count0

Step through to see values update.

ConsoleNo output yet
Press Start to begin stepping through the code.
Initial state

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?

SituationDeclare as
Only needed inside this one callLocal variable (inside the method)
Needs to survive between calls to periodic()Class field
Needs to be shared across multiple subsystemsPassed as a parameter, or stored in a shared class
Note

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.

⚠ Heads up

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

⚡ 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;
}
Code Tracer
1double speed = 0.5; // field
2
3void periodic() {
4 double boost = 0.0;
5 if (turboHeld) boost = boost + 0.2;
6 speed = speed + boost;
7}
State
call1
speed0.5

Step through to see values update.

Press Start to begin stepping through the code.
Initial state
⚡ Check your understanding

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

⚡ Try it yourself

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.

Code EditorJavaCtrl+Enter to run
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.