The Command Pattern
An action as an object
Everything in this module has been building toward this. A command is an object whose purpose is to represent something to be done, rather than doing it immediately.
interface Command {
void execute();
}
That is the minimal form. Because the action is an object, you can put it in a list, hand it to another method, keep it for later, or store enough information to reverse it.
Command spinUp = () -> shooter.set(0.9);
Command stop = () -> shooter.set(0.0);
Neither has run. They are values describing work, created with lambdas.
Why not just call the method?
If you only ever want the action to happen right now, call it — the pattern is unnecessary overhead.
It earns its place when you need one of these:
| Requirement | Why a plain method call fails |
|---|---|
| Run it later | A call happens immediately |
| Undo it | A call leaves nothing to reverse |
| Queue several in order | Calls cannot be stored |
| Let a user bind it to a button | The binding needs a value, not a call |
| Combine actions into sequences | There is nothing to combine |
| Decide at runtime which action to run | Requires branching at every site |
Adding lifecycle
A single execute() is enough for instant actions, but most real work takes several cycles — driving to a position, spinning a flywheel up to speed. That needs a richer shape:
interface Command {
default void initialize() {} // once, at the start
void execute(); // repeatedly, each cycle
default boolean isFinished() { // when should it stop?
return false;
}
default void end(boolean interrupted) {} // once, at the end
}
default methods let an implementation override only what it cares about. A simple command supplies execute and nothing else.
class SpinUpShooter implements Command {
private final Shooter shooter;
private final double targetRpm;
SpinUpShooter(Shooter shooter, double targetRpm) {
this.shooter = shooter;
this.targetRpm = targetRpm;
}
@Override public void initialize() {
shooter.setTarget(targetRpm);
}
@Override public void execute() {
shooter.update();
}
@Override public boolean isFinished() {
return shooter.atTargetSpeed();
}
@Override public void end(boolean interrupted) {
if (interrupted) shooter.stop();
}
}
The dependencies arrive through the constructor — dependency injection — so this class is testable with a fake shooter.
end() takes a boolean for a reason
A command can finish two ways: it completed, or something cut it short. Those often need different cleanup — a completed shooter might hold its speed, while an interrupted one should stop.
Passing interrupted lets one method handle both without the caller needing two.
A scheduler
Something has to run these. The loop is short:
class Scheduler {
private final List<Command> running = new ArrayList<>();
void schedule(Command command) {
command.initialize();
running.add(command);
}
void run() {
Iterator<Command> it = running.iterator();
while (it.hasNext()) {
Command c = it.next();
c.execute();
if (c.isFinished()) {
c.end(false);
it.remove(); // safe removal during iteration
}
}
}
void cancelAll() {
for (Command c : running) {
c.end(true); // interrupted
}
running.clear();
}
}
Using an Iterator with it.remove() is what allows removal mid-loop. A for-each with running.remove(c) would throw ConcurrentModificationException — the trap from the ArrayLists lesson.
Call run() once per cycle and every scheduled command advances one step.
Composing commands
Because commands are objects, you can build commands out of other commands. This is where the pattern becomes genuinely powerful.
class SequentialCommand implements Command {
private final List<Command> steps;
private int index = 0;
SequentialCommand(Command... steps) {
this.steps = List.of(steps);
}
@Override public void initialize() {
index = 0;
if (!steps.isEmpty()) steps.get(0).initialize();
}
@Override public void execute() {
if (index >= steps.size()) return;
Command current = steps.get(index);
current.execute();
if (current.isFinished()) {
current.end(false);
index++;
if (index < steps.size()) {
steps.get(index).initialize();
}
}
}
@Override public boolean isFinished() {
return index >= steps.size();
}
}
SequentialCommand is a Command, so it can be scheduled like any other — or nested inside another sequence.
Command autonomous = new SequentialCommand(
new DriveDistance(drivetrain, 2.0),
new SpinUpShooter(shooter, 4000),
new Shoot(shooter, feeder),
new DriveDistance(drivetrain, -2.0)
);
scheduler.schedule(autonomous);
An entire autonomous routine is one object. A parallel version — running several at once and finishing when all are done — follows the same shape.
This is what FRC command-based programming is
If you write FRC code, you have used this pattern already. WPILib’s Command, CommandScheduler, SequentialCommandGroup, and ParallelCommandGroup are exactly the structures above, with more features and careful edge-case handling.
Two additions worth knowing about in the real framework:
- Requirements. A command declares which subsystems it uses, and the scheduler refuses to run two commands needing the same subsystem — preventing two commands fighting over one motor.
- Decorators. Methods like
andThen,alongWith, andwithTimeoutbuild composite commands without you writing the group classes.
The pattern here is the foundation; the framework is this plus safety.
Undo
Where the pattern is used for editing rather than scheduling, add a reverse operation:
interface UndoableCommand {
void execute();
void undo();
}
class SetPosition implements UndoableCommand {
private final Arm arm;
private final double target;
private double previous;
SetPosition(Arm arm, double target) {
this.arm = arm;
this.target = target;
}
@Override public void execute() {
previous = arm.getPosition(); // remember, so undo is possible
arm.moveTo(target);
}
@Override public void undo() {
arm.moveTo(previous);
}
}
Push executed commands onto a stack; undo pops and reverses. That is how every editor’s undo works.
Common mistakes
- Modifying the running list during a for-each. Use an
Iterator, or collect finished commands and remove them afterwards. - A command that never finishes. If
isFinished()always returnsfalse, it runs forever. Give long-running commands a timeout. - Doing setup in the constructor instead of
initialize(). A command may be scheduled more than once, and the constructor runs only the first time — so state from a previous run leaks in. Reset ininitialize(). - Forgetting to reset state, which breaks the second run of a reused command.
- Sharing a mutable command between two schedulers, so both mutate the same
index. - Capturing a value instead of a
Supplierfor inputs that change. - Using the pattern for a single immediate call, adding indirection for nothing.
Practice
- Define a one-method
Commandinterface and create three commands with lambdas that print different messages. Store them in a list and run them in order. - Add
initialize,isFinished, andendasdefaultmethods, then write a command that counts to five before finishing. - Write a
Schedulerthat runs commands each cycle and removes finished ones. Test it with the counting command. - Write a
SequentialCommandrunning several commands one after another, and confirm a sequence can contain another sequence. - Write an
UndoableCommandfor setting a value, and an undo stack supporting repeated undo.
Hints
List<Command>and a for-each callingexecute().- A counter field incremented in
execute;isFinishedreturns true at five. Reset the counter ininitialize, not the constructor — exercise 3 will show you why. - The
Iteratorloop above. Schedule the counting command and callrun()six times. - Track an index of the current step. Nesting works automatically because the group implements the same interface.
- Store the previous value during
execute. Push each command onto anArrayDeque; undo pops and callsundo().