Command-based is powerful but has sharp edges that bite nearly every new programmer. Here are the most common ones and the fix.
Mistake 1: Default command without a requirement
A default command must require its subsystem, or the scheduler throws an IllegalArgumentException complaining that default commands must require their subsystem. The fix is almost always to build the command from the subsystem's own factory (run, runOnce) so the requirement is added automatically:
// Wrong: a bare command that doesn't require the drivetrain
m_drive.setDefaultCommand(new RunCommand(() -> m_drive.stop()));
// Right: factory on the subsystem adds the requirement for you
m_drive.setDefaultCommand(m_drive.run(() -> m_drive.stop()));
Mistake 2: Forgetting requirements anywhere
If two commands touch the same subsystem but only one declares the requirement, the scheduler won't know they conflict and both run -- fighting over the motor. Every command that reads or writes a subsystem must list it in addRequirements() (or be built from that subsystem's factory). Correct requirements are what let the scheduler deschedule the default command when a real command is triggered.
Mistake 3: Reusing a single command instance
A Command object can only be scheduled in one place at a time. Storing one instance and binding it to two triggers causes confusing 'command already scheduled' behavior. Prefer command factories -- methods that return a new command each call:
public Command scoreL4() { return runOnce(() -> setGoal(L4)); }
Mistake 4: Putting logic in default commands
Keep default commands trivial -- stop or hold position. Decision logic belongs in triggers composed with and(), or(), negate(), and smoothed with debounce(). Frame triggers as problem-domain yes/no questions (hasGamePiece, isAtSpeed), and never expose raw sensor values to RobotContainer.
Mistake 5: Comparing floats with ==
encoder.getPosition() == 10.0 is almost never true. Use a tolerance:
if (MathUtil.isNear(10.0, encoder.getPosition(), 0.05)) { ... }
Mistake 6: Expensive work repeated per trigger
Every trigger condition is polled each loop. If a trigger calls an expensive method (a CAN query, a vision computation), and several triggers ask the same question, you pay that cost repeatedly. Compute it once per loop in periodic(), cache it, and have triggers read the cached value.
Getting these six right eliminates the majority of 'the robot does weird things' command bugs before they ever reach the field.
Key takeaways
- Default commands must require their subsystem -- build them from the subsystem's run()/runOnce() factories.
- Every command must declare requirements so the scheduler can resolve conflicts and deschedule defaults.
- Use command factories that return new instances; never reuse one command object across bindings.
- Keep logic in debounced triggers (not default commands), use MathUtil.isNear() for floats, and cache expensive trigger values.
Keep going
Take the quiz · +10 XP with an accountMore in Common Mistakes and Troubleshooting
Sources & corrections
This lesson is AI-assisted: drafted from primary sources, then reviewed and edited by hand. Errors still get through. When one is reported we fix it and write down what changed — publicly, in the corrections log.
Sources and further reading
- BoVLB: Command-Based Best Practicesbovlb.github.io
- WPILib: Commandsdocs.wpilib.org
- WPILib: Subsystemsdocs.wpilib.org
Read next
Articles on this, for competition day
The lesson covers how it works. These cover what to do when it breaks.
- 9 min read
FRC Command-Based Programming: Subsystems, Commands, and the Scheduler
A beginner-friendly guide to WPILib command-based programming in Java: subsystems, commands, the CommandScheduler, triggers, and composing commands.
Read - 13 min read
FRC Code Structure Best Practices: Command-Based Project Architecture
How to structure an FRC command-based robot project the right way — subsystems, commands, RobotContainer, and Constants — verified against official WPILib docs.
Read
Lesson quiz
RequiredAll 4 right completes the lesson. Miss one and only that question comes back — anything you already answered correctly stays banked.
0 of 4 answered
01.A default command throws IllegalArgumentException for missing a requirement. What is the recommended fix?
02.Why should you avoid storing one command instance and binding it to two different triggers?
03.Comparing a float like encoder.getPosition() == 10.0 is almost never true. What should you do instead?
04.Several triggers each call an expensive CAN or vision method every loop. How should you avoid paying that cost repeatedly?
Answer every question to submit.
All 51 lessons in Programming, Controls & Sensors
- Not started:Mini-Project: A Closed-Loop Elevator with Motion Magic
- Not started:Mini-Project: A Velocity-Controlled Shooter on REVLib
- Not started:Mini-Project: A Teleop Swerve Drive Subsystem
- Not started:Mini-Project: An Autonomous Routine with PathPlanner
- Not started:Mini-Project: Vision-Aligned Scoring with Limelight
- Not started:State-Space Control and Kalman Filtering
- Not started:Log Replay Architecture with AdvantageKit
- Not started:Advanced Pose Estimation: Multi-Tag Fusion and Standard Deviations
- Not started:Robot Coordination, Alerts, and Operator Feedback
- Not started:Case Study: Hardening Software Before an Event