Skip to content

Zero-Allocation Programming

Zero-Allocation programming is a prime example of Innovation meeting elite performance. In this guide, we’ll discover how to overcome the inherent limitations of the Java Virtual Machine to ensure our robot code is deterministic, safe, and ready for the highest level of Impact. By mastering these patterns, you ensure that our collective Teamwork is never interrupted by a garbage collector pause.


In simple terms: Zero-allocation means reusing objects instead of creating new ones.

Think of it like this:

  • With allocation: Every time you need a cup, you buy a new one.
  • Zero-allocation: You wash and reuse the same cup.

Why it matters: Creating objects takes time and memory. Your robot needs to respond quickly - every 20 milliseconds! Creating too many objects makes your robot slow and stuttery.


Java’s Garbage Collector (GC) is like a trash service that cleans up objects you no longer need.

The problem:

  • When GC runs, it pauses everything else.
  • Your robot stops responding during GC.
  • This causes missed shots, crashes, and inconsistent performance.

Real impact:

  • Your auto routine might miss the target.
  • Robot stutters during matches.
  • Unpredictable behavior at critical moments.

FRC robots run on a tight schedule:

  • Main loop: Every 20ms (50 times per second)
  • Odometry loop: Every 4ms (250 times per second)

If GC runs:

  • Your 20ms loop might take 40ms or longer.
  • Robot’s position becomes inaccurate.
  • Robot might crash or miss critical shots.

Instead of creating new objects, reuse the same ones.

@Override
public void periodic() {
// BAD: Creates new Translation2d every 20ms!
Translation2d position = new Translation2d(x, y);
// More bad object creation
Pose2d robotPose = new Pose2d(position, new Rotation2d(heading));
}

Why this is bad: Each new creates trash. The GC has to clean it up later.

// Create once, reuse forever
private final Translation2d position = new Translation2d(0, 0);
private final Pose2d robotPose = new Pose2d();
@Override
public void periodic() {
// Reuse the same objects
position.setX(x);
position.setY(y);
robotPose.setPose(position, new Rotation2d(heading));
}

Why this is good: No trash created, no GC needed, runs fast and smooth!


Never do these in periodic() or any loop:

// BAD: Creates new object every loop
Pose2d currentPose = new Pose2d(x, y, new Rotation2d(theta));
// GOOD: Reuse object
private final Pose2d currentPose = new Pose2d();
// In periodic(): currentPose.setX(x); currentPose.setY(y);
// BAD: Creates new lambda every loop
commands.add(() -> drivetrain.drive(0.5));
// GOOD: Create command once
private final DriveCommand driveCommand = new DriveCommand(drivetrain, 0.5);
commands.add(driveCommand);
// BAD: Creates new strings every loop
String status = "Mode: " + mode + " Speed: " + speed;
// GOOD: Use Logger or pre-build
Logger.recordOutput("Status", mode);
// Or use StringBuilder
private final StringBuilder statusBuilder = new StringBuilder(50);
statusBuilder.setLength(0); // Clear
statusBuilder.append("Mode: ").append(mode);

Use the simulator below to see how object creation affects loop time:

What to notice:

  • Click “ALLOCATE OBJECT” and watch loop time spike.
  • Watch GC pause everything when trash piles up.
  • Compare to “REUSE OBJECT” - smooth and fast!

MARSLib handles zero-allocation for you using IO Inputs.

IO Inputs are pre-built objects that hold all your data:

// MARSLib creates these for you
public static class DrivetrainInputs {
public double positionX;
public double positionY;
public double velocityX;
public double velocityY;
// ... more data
}
// You just update values
@Override
public void periodic() {
inputs.positionX = odometry.getX();
inputs.positionY = odometry.getY();
// No new objects created!
}

Why this works:

  • Inputs objects created once at startup.
  • You just update the values.
  • Zero trash, zero GC, maximum speed!

Benefits you get:

  1. Faster loop times: Your code runs quicker.
  2. Smoother operation: No GC pauses.
  3. Reliable auto: Auto routines work every time.
  4. Better performance: Can run more complex code.

Real-world results:

  • Loop times: 5ms instead of 15ms.
  • No stuttering or lag.
  • Auto accuracy improves dramatically.
  • Robot responds predictably.

// BAD
String message = "Robot at: " + pose.getX() + ", " + pose.getY();
// GOOD - Use Logger
Logger.recordOutput("Robot/X", pose.getX());
Logger.recordOutput("Robot/Y", pose.getY());
// BAD
Translation2d movement = new Translation2d(dx, dy);
// GOOD - Update existing
private final Translation2d movement = new Translation2d(0, 0);
// In periodic():
movement.setX(dx);
movement.setY(dy);
// BAD
List<Double> values = new ArrayList<>();
// GOOD - Reuse list
private final List<Double> values = new ArrayList<>();
// In periodic():
values.clear();
values.add(speed1);
values.add(speed2);

Check for allocations:

Terminal window
# Search for new keywords in periodic methods
grep -n "new " src/main/java/frc/robot/subsystems/
@Override
public void periodic() {
double startTime = Timer.getFPGATimestamp();
// Your code here
double loopTime = (Timer.getFPGATimestamp() - startTime) * 1000;
SmartDashboard.putNumber("LoopTime_ms", loopTime);
// Warning if too slow
if (loopTime > 10.0) {
DriverStation.reportWarning("Loop too slow: " + loopTime + "ms", false);
}
}

Good: Loop time < 10ms Warning: Loop time 10-15ms Problem: Loop time > 15ms

Terminal window
# Run with profiling
java -XX:+PrintGCDetails -XX:+PrintGCTimeStamps YourRobot

Look for GC pauses in the output.


In loops (periodic, execute, etc.):

  • ✅ Reuse objects.
  • ✅ Update values instead of creating new.
  • ✅ Use Logger for output.
  • ❌ No new keywords.
  • ❌ No lambdas
  • ❌ No string building.

Outside loops:

  • ✅ Create objects once in constructor.
  • ✅ Use static final constants.
  • ✅ Pre-allocate collections.

public class Drivetrain {
public void periodic() {
// Creates 3 new objects every 20ms!
Pose2d pose = new Pose2d(odometry.getX(), odometry.getY(),
new Rotation2d(odometry.getHeading()));
ChassisSpeeds speeds = new ChassisSpeeds(forward, strafe, rotation);
SwerveModuleState[] states = kinematics.toSwerveModuleStates(speeds);
// ... more code
}
}

Problems:

  • Creates 3 objects every 20ms.
  • 150 objects per second.
  • Causes GC pauses.
  • Loop time: 12-15ms.
public class Drivetrain {
// Create once, reuse forever
private final Pose2d pose = new Pose2d();
private final ChassisSpeeds speeds = new ChassisSpeeds(0, 0, 0);
private final SwerveModuleState[] states = new SwerveModuleState[4];
public void periodic() {
// Just update values
pose.setX(odometry.getX());
pose.setY(odometry.getY());
pose.setRotation(new Rotation2d(odometry.getHeading()));
speeds.vxMetersPerSecond = forward;
speeds.vyMetersPerSecond = strafe;
speeds.omegaRadiansPerSecond = rotation;
// ... more code
}
}

Benefits:

  • Zero objects created.
  • No GC pauses
  • Loop time: 3-5ms.
  • Robot runs smoothly!

Before competition:

  1. Profile your code (check loop times)
  2. Look for allocations in loops.
  3. Fix any issues you find.
  4. Test under load (run for 5+ minutes)

During competition:

  • Monitor loop times on dashboard.
  • Watch for performance warnings.
  • Keep code clean and fast.

Debugging slow code:

  1. Add timing prints to find slow code.
  2. Check for new keywords.
  3. Look for string building.
  4. Profile each subsystem.

For complex objects, use a pool:

public class ObjectPool<T> {
private final Queue<T> pool = new LinkedList<>();
public T get() {
T obj = pool.poll();
if (obj == null) {
obj = createNew();
}
return obj;
}
public void return(T obj) {
pool.offer(obj);
}
}

Pre-allocate large structures:

// Allocate once
private final double[] sensorData = new double[100];
// Use many times
@Override
public void periodic() {
for (int i = 0; i < sensorCount; i++) {
sensorData[i] = sensors[i].get();
}
// No new arrays created!
}

Key points to remember:

  1. Zero-allocation = Reuse objects, don’t create new ones.
  2. GC pauses make your robot slow and unpredictable.
  3. MARSLib IO inputs handle this for you automatically.
  4. Avoid new, lambdas, and string building in loops.
  5. Monitor loop times to catch performance issues.

Your robot will:

  • Run faster and smoother.
  • Respond more predictably.
  • Perform better in autonomous.
  • Be more reliable in competition.


Remember: Fast code wins competitions! Zero-allocation programming is one of the most important skills for competitive FRC programming.

Need help optimizing your code? Ask our community