Unit 2 · Lesson 1

Variables

Every number in your robot code controls something you could touch, measure, or break. Variables are how we give those numbers a name, a type, and a home — so that when something goes wrong at 9 AM on the competition field, you can find it and fix it in under two minutes.

By the end of this lesson, you will:

  • Declare and initialize variables using correct Java syntax
  • Explain the difference between a variable's type, name, and value
  • Describe how the four most common Java types map to real robot hardware
  • Recognize when to use a local variable versus an instance variable in a robot loop
  • Apply the naming conventions used in professional FRC code — including Team 2910's standards

Why Variables? A Field Story

Picture this: it's 9 AM at a regional, and your robot's intake is running too fast — it's spitting out game pieces instead of holding them. A mentor walks up and asks, "What speed is the intake set to?" Your programmer shrugs and says, "I don't know, I just put 0.8 in there." Now you have to read through all of teleopPeriodic() to find where the motor is being set, figure out which of the three 0.8 values is the intake, change it, rebuild, and redeploy — in a ten-minute pit window, with matches starting.

That is a variable problem. Or more precisely, the absence of one. A variable is how a number gets a name. And a name is how your team can find it, understand it, and change it without breaking three other things in the process.

🔍 LRI Observation

As a Lead Robot Inspector, one of the most common things I see in struggling robots is the same number appearing in multiple places in the code — sometimes with slightly different values, because the programmer edited one copy and not the others. During inspection that's a warning sign. During a match, it's usually the root cause of a fault that nobody can reproduce reliably. One well-named variable would have prevented it.

Anatomy of a Variable Declaration

In Java, every variable has exactly three required pieces. Click each part of the declaration below to learn what it does and why it matters on a robot.

Variable Declaration Explorer
double type   intakeSpeed name  =  0.6 value ;
← click any part of the declaration to learn what it does

The pattern is always the same: type name = value;. The semicolon at the end is not optional — Java is not Python. Missing a semicolon is one of the five most common compiler errors you will see this year. The compiler will tell you where it expected one, and it will be right.

The Four Types You'll Use Every Day

Java has many data types, and Unit 2, Lesson 2 covers them in full depth. For now, here are the four that appear in almost every FRC program you will ever write. Understanding what they store physically — not just abstractly — is what keeps you from making the mistakes that are invisible to the compiler but fatal to the robot.

👇 Click each card to see the robot context
🔢
double
tap to see robot context
double

Decimal numbers. Motor outputs (−1.0 to 1.0), PID gains, encoder positions in degrees, and wheel speeds in RPM are all double. When in doubt and the value might ever be fractional, use double.

#️⃣
int
tap to see robot context
int

Whole numbers. CAN bus IDs, PWM port numbers, current limits in amps, and button IDs on a controller are all int. You can't have CAN ID 2.7 — it's always a whole number.

🔀
boolean
tap to see robot context
boolean

True or false only. Limit switch state, beam break sensor readings, "is the intake running," "has the arm been zeroed" — anything that is a yes/no question about the physical world.

🔤
String
tap to see robot context
String

Text. Subsystem names on Shuffleboard, fault messages to log, and chooser labels for auto selection are all String. Note the capital S — String is a class, not a primitive type.

💡 The int vs. double trap

Motor controllers expect a double in the range of −1.0 to 1.0. If you accidentally declare your speed as an int and initialize it to 0, your motor will not move — because integer 0 and double 0.0 both pass to motor.set() just fine. The compiler won't complain. The robot just won't do anything, and you'll spend ten minutes checking wiring before noticing the type.

Naming Variables: The 2910 Standard

Variable names are not just for the compiler — they're for every programmer who touches that file after you, including the student who inherits the robot in two years. A good name is the cheapest documentation you will ever write.

The rules

  • Use camelCase. Start with a lowercase letter; capitalize the first letter of each subsequent word. intakeSpeed, not intakespeed, not IntakeSpeed.
  • Name the physical thing it controls. If you read the name in six months with no context, you should know exactly what motor, sensor, or state it represents.
  • Constants get ALL_CAPS_SNAKE_CASE. Values that should never change during a match are declared static final and written in uppercase with underscores. We'll cover the static keyword properly in Unit 4, but you'll see this pattern immediately in real code.
double a = 0.8; What is a?

A letter tells you nothing. Motor speed? Arm angle? Battery voltage? A mentor reading this at 10 PM the night before a competition has no idea what to change or why.

double temp = 40; A current limit hiding as "temp"

This is actually the current limit in amps. The name says it's temporary and meaningless. A current limit is neither — it's a hardware protection value and it deserves a proper name.

double shooterWheelSpeedRPM = 3500.0; Clear and specific

The mechanism (shooter wheel), what it describes (speed), and the unit (RPM) are all in the name. No guesswork. No comments needed to explain it.

static final int INTAKE_CURRENT_LIMIT_AMPS = 40; A constant done right

static final tells the compiler and every reader this value won't change. The name tells you the mechanism, the thing it limits, and the unit. Change it once, everywhere updates.

🔍 Event Observation

I've inspected robots where the same motor's current limit was set to 80 amps in one method and 40 amps in another — because the programmer copy-pasted a number and changed one but not the other. The robot passed inspection both times because neither limit was technically illegal. It browned out in its first real match because 80 amps on that motor was higher than the breaker. One well-named static final constant, referenced in both places, would have made it physically impossible for those two values to differ.

Where Variables Live: Scope

Scope defines where in your code a variable exists and can be used. In most programming contexts this is a mild annoyance. In robot code, where the same methods are called 50 times per second, getting scope wrong produces bugs that are nearly impossible to reproduce because they depend on timing.

A local variable is declared inside a method. It is created when the method is called and destroyed when the method returns. In a TimedRobot, that means it's recreated from scratch every 20 milliseconds.

Use a local variable when the value only needs to exist for the duration of a single loop iteration — like reading a joystick and immediately passing it to a motor.

// This variable is born and dies every 20ms @Override public void teleopPeriodic() { double joystickY = driverController.getLeftY(); // local driveMotor.set(joystickY); // joystickY no longer exists after this method returns }

The key thing to internalize: a local variable cannot remember anything between loop cycles. Every time teleopPeriodic() runs, joystickY starts fresh. That's fine here — we want the current joystick value, not last frame's value.

An instance variable (also called a field) is declared inside the class but outside any method. It persists for the entire lifetime of the object — in practice, for the entire match. This is where you store things that the robot needs to remember across loop cycles.

Use an instance variable when you need to track state over time: is the intake currently running? Has the arm been zeroed? Was a fault logged in the previous cycle?

public class Robot extends TimedRobot { // Instance variable — survives across ALL loop calls private boolean intakeRunning = false; @Override public void teleopPeriodic() { if (driverController.getAButtonPressed()) { intakeRunning = true; // remembered next cycle } if (driverController.getBButtonPressed()) { intakeRunning = false; // remembered next cycle } if (intakeRunning) { intakeMotor.set(INTAKE_SPEED_FORWARD); } else { intakeMotor.set(0.0); } } }

If intakeRunning were a local variable, it would reset to false every 20ms and the toggle would never work. The physical mechanism would never actually run. This is a scope bug — and the robot compiles and deploys perfectly while doing completely the wrong thing.

Putting It Together: Variables in a Real Robot

Here is a complete intake example that uses all three kinds of variables — constants, instance variables, and local variables — each doing exactly the job it should do. Read through the comments carefully; this is the pattern you'll use in almost every subsystem you write.

package frc.robot; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.XboxController; import com.ctre.phoenix6.hardware.TalonFX; public class Robot extends TimedRobot { // ── Hardware ───────────────────────────────────────────────────── private final TalonFX intakeMotor = new TalonFX(0); private final XboxController driverController = new XboxController(0); // ── Constants — never change at runtime ────────────────────────── // Change these once here; every reference below updates automatically private static final double INTAKE_SPEED_FORWARD = 0.6; private static final double INTAKE_SPEED_REVERSE = -0.4; private static final int INTAKE_CURRENT_LIMIT = 40; // amps // ── Instance variables — remembered across loop cycles ─────────── private boolean intakeRunning = false; // default: safe/stopped private double lastIntakeOutput = 0.0; // for logging @Override public void teleopPeriodic() { // Local variable — recalculated fresh every 20ms double intakeOutput = 0.0; if (driverController.getRightBumper()) { intakeOutput = INTAKE_SPEED_FORWARD; intakeRunning = true; } else if (driverController.getLeftBumper()) { intakeOutput = INTAKE_SPEED_REVERSE; intakeRunning = false; } else { intakeOutput = 0.0; intakeRunning = false; } intakeMotor.set(intakeOutput); lastIntakeOutput = intakeOutput; // save for next cycle's logging } }

Notice that INTAKE_SPEED_FORWARD appears exactly once. If a mentor says "slow the intake down a little," you change one number in one place and every reference updates. If that number were scattered across five motor.set(0.6) calls, you'd hunt for all of them — and you'd probably miss one, and have different speeds on different bumpers, and spend a match wondering why the intake behaves inconsistently.

🔌 System Check

⚙️ What to Verify Before You Run Variable-Driven Code

Variables are laptop-side work — no robot required to learn the syntax. But before your variables connect to real hardware, verify these:

  • Every variable connected to a motor has a physically meaningful name. If a teammate can't tell what mechanism it controls from the name alone, rename it before merging into main.
  • Current limit constants must be lower than the breaker rating. A NEO stalls at ~105A. Your 40A breaker will trip first — and it should. Set your software limit below 40A so the software stops the motor before the breaker does.
  • Boolean state variables must default to the safe state. boolean intakeRunning = false is safe — the intake starts stopped. boolean intakeRunning = true means the motor runs the moment the robot enables. Guess which one causes pit accidents.
  • Use a Constants.java file as the single source of truth. All port numbers, current limits, and tunable values live there. If the same number appears in more than one file as a literal, that's a bug waiting to happen.
  • Sensor-driven variables need working sensors. A disconnected encoder returns 0, which looks like a valid position. Before trusting any variable that reads from hardware, confirm the hardware is plugged in and returning reasonable values.

Knowledge Check

Click an answer to check your understanding.

A student writes int shooterSpeed = 0; and then calls shooterMotor.set(shooterSpeed). The code compiles and deploys. The shooter wheel doesn't spin. What is most likely the problem?
  • 1The motor controller's CAN ID is wrong
  • 2The variable is declared as int instead of double, and integer 0 tells the motor to stay stopped — there's no type error, but the value never gets set to anything that would actually spin the motor
  • 3The variable needs to be declared static final to work with a motor
  • 4The code has a missing semicolon that the compiler missed
A programmer declares boolean intakeRunning = false; inside teleopPeriodic() instead of as a class-level field. The A button is supposed to toggle the intake on. What will happen at runtime?
  • 1The compiler will throw an error because booleans can't be used inside periodic methods
  • 2The toggle will work correctly — the value is preserved between calls
  • 3The intake will never stay on — the variable resets to false every 20ms, so pressing A sets it to true for exactly one loop cycle before it resets
  • 4The intake will get stuck in the on state and never turn off
Your code has motor.set(0.7) in three different places. A mentor asks you to reduce the drive speed for a tighter field layout. What is the risk of this approach compared to using a named constant?
  • 1There is no risk — the compiler will find all three and update them automatically
  • 2The robot will brownout if the same number is used more than twice
  • 3You have to manually find and change all three copies — and if you miss one, the robot will behave differently in different situations with no obvious reason why, which is extremely hard to debug under competition pressure
  • 4Using the same number in multiple places will cause a compilation error in a future WPILib version
💪 Practice Prompt

Refactor the Magic Numbers

Open WPILib VS Code and create a new Java file called IntakePractice.java. Start with the code below — it works, but it has five magic numbers that would cause real problems on a competition field.

// Starting point — do not turn in this version public void teleopPeriodic() { if (controller.getRightBumper()) { intakeMotor.set(0.75); } else if (controller.getLeftBumper()) { intakeMotor.set(-0.4); } else { intakeMotor.set(0.0); } if (intakeMotor.getStatorCurrent().getValueAsDouble() > 60.0) { intakeMotor.set(0.0); } }
  1. Identify all five magic numbers and write a sentence for each explaining what physical thing it controls.
  2. Declare a static final constant for each one at the top of the class with an appropriate name and type. Add a comment explaining the physical significance of the value — not just what it is, but why that specific number.
  3. Replace all five literals in teleopPeriodic() with your new constants.
  4. Add an instance variable to track whether the intake is currently running, and update it correctly in all three branches.
  5. Bonus: Find Team 2910's most recent robot repository on GitHub. Open Constants.java (or the equivalent). Choose any five constants and write a sentence explaining what you think each one controls and why that value was probably chosen.